bae705aa97
- Add NFC ePassport roadmap (ICAO 9303, eIDAS) - Add TensorFlow.js edge face detection (BlazeFace) - Add structured audit logger (GDPR-compliant) - Risk scoring support Part of KYC Apple Native UX v1.1.0
299 lines
7.5 KiB
JavaScript
299 lines
7.5 KiB
JavaScript
/**
|
|
* QUIXZOOM Video Pipeline — Contradiction Alert System
|
|
*
|
|
* Övervakar kontradiktioner i realtid och skickar varningar
|
|
* när gapet mellan förväntad och observerad verklighet ökar.
|
|
*
|
|
* Konfigurerbara tröskelvärden:
|
|
* - Low: 0-20 (grön)
|
|
* - Moderate: 21-40 (gul)
|
|
* - High: 41-60 (orange)
|
|
* - Critical: 61-100 (röd)
|
|
*/
|
|
|
|
const Redis = require('ioredis');
|
|
|
|
const redis = new Redis({
|
|
host: process.env.REDIS_HOST || 'localhost',
|
|
port: process.env.REDIS_PORT || 6379,
|
|
});
|
|
|
|
/**
|
|
* Alert-konfiguration
|
|
*/
|
|
const ALERT_CONFIG = {
|
|
thresholds: {
|
|
low: { min: 0, max: 20, color: 'green', action: 'none' },
|
|
moderate: { min: 21, max: 40, color: 'yellow', action: 'monitor' },
|
|
high: { min: 41, max: 60, color: 'orange', action: 'alert' },
|
|
critical: { min: 61, max: 100, color: 'red', action: 'critical' },
|
|
},
|
|
|
|
// Vilka kontradiktioner ska övervakas
|
|
watchedContradictions: [
|
|
{
|
|
id: 'commercial_vs_infrastructure',
|
|
name: 'Commercial Vitality vs Infrastructure',
|
|
description: 'High commercial activity but failing infrastructure',
|
|
layers: ['commercial_vitality', 'infrastructure'],
|
|
threshold: 25, // Poängdifferens som triggar varning
|
|
},
|
|
{
|
|
id: 'global_vs_informal',
|
|
name: 'Global Presence vs Informal Economy',
|
|
description: 'Multinationals operating alongside unregulated infrastructure',
|
|
layers: ['economic', 'institutional'],
|
|
threshold: 20,
|
|
},
|
|
{
|
|
id: 'physical_vs_operational',
|
|
name: 'Physical Condition vs Activity Level',
|
|
description: 'High activity in deteriorating physical environment',
|
|
layers: ['physical', 'operational'],
|
|
threshold: 20,
|
|
},
|
|
{
|
|
id: 'investment_vs_growth',
|
|
name: 'Investment Confidence vs Growth',
|
|
description: 'Growth without corresponding investment confidence',
|
|
layers: ['investment_confidence', 'growth'],
|
|
threshold: 15,
|
|
},
|
|
],
|
|
|
|
// Notifieringskanaler
|
|
notifications: {
|
|
email: process.env.ALERT_EMAIL,
|
|
slack: process.env.ALERT_SLACK_WEBHOOK,
|
|
telegram: process.env.ALERT_TELEGRAM_BOT,
|
|
},
|
|
|
|
// Cooldown mellan varningar (minuter)
|
|
cooldownMinutes: 30,
|
|
};
|
|
|
|
/**
|
|
* Beräkna kontradiktion mellan två lager
|
|
*/
|
|
function calculateContradiction(score1, score2) {
|
|
const diff = Math.abs(score1 - score2);
|
|
const max = Math.max(score1, score2);
|
|
|
|
// Normalisera till 0-100
|
|
return Math.min(100, (diff / max) * 100);
|
|
}
|
|
|
|
/**
|
|
* Klassificera kontradiktionsnivå
|
|
*/
|
|
function classifyContradiction(index) {
|
|
for (const [level, config] of Object.entries(ALERT_CONFIG.thresholds)) {
|
|
if (index >= config.min && index <= config.max) {
|
|
return { level, ...config };
|
|
}
|
|
}
|
|
return { level: 'unknown', color: 'gray', action: 'none' };
|
|
}
|
|
|
|
/**
|
|
* Kontrollera om varning ska skickas
|
|
*/
|
|
async function shouldSendAlert(contradictionId, level) {
|
|
const key = `quixzoom:alert:cooldown:${contradictionId}`;
|
|
const lastAlert = await redis.get(key);
|
|
|
|
if (lastAlert) {
|
|
const lastTime = new Date(lastAlert);
|
|
const now = new Date();
|
|
const diffMinutes = (now - lastTime) / 1000 / 60;
|
|
|
|
if (diffMinutes < ALERT_CONFIG.cooldownMinutes) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Sätt cooldown
|
|
await redis.set(key, new Date().toISOString());
|
|
await redis.expire(key, ALERT_CONFIG.cooldownMinutes * 60);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Skicka varning
|
|
*/
|
|
async function sendAlert(alert) {
|
|
console.log(`[ALERT] ${alert.level.toUpperCase()}: ${alert.title}`);
|
|
console.log(`[ALERT] ${alert.description}`);
|
|
console.log(`[ALERT] City: ${alert.city}, Score: ${alert.contradictionIndex}`);
|
|
|
|
// Spara till Redis
|
|
await redis.lpush('quixzoom:alerts', JSON.stringify({
|
|
...alert,
|
|
timestamp: new Date().toISOString(),
|
|
}));
|
|
|
|
// Behåll senaste 1000 varningar
|
|
await redis.ltrim('quixzoom:alerts', 0, 999);
|
|
|
|
// TODO: Skicka till notifieringskanaler
|
|
// await sendEmail(alert);
|
|
// await sendSlack(alert);
|
|
// await sendTelegram(alert);
|
|
}
|
|
|
|
/**
|
|
* Huvudfunktion — övervaka kontradiktioner
|
|
*/
|
|
async function monitorContradictions(city, scores) {
|
|
const alerts = [];
|
|
|
|
for (const config of ALERT_CONFIG.watchedContradictions) {
|
|
const score1 = scores[config.layers[0]];
|
|
const score2 = scores[config.layers[1]];
|
|
|
|
if (score1 === undefined || score2 === undefined) continue;
|
|
|
|
const index = calculateContradiction(score1, score2);
|
|
const classification = classifyContradiction(index);
|
|
|
|
// Kontrollera om tröskelvärdet är passerat
|
|
const diff = Math.abs(score1 - score2);
|
|
|
|
if (diff >= config.threshold) {
|
|
const shouldAlert = await shouldSendAlert(config.id, classification.level);
|
|
|
|
if (shouldAlert) {
|
|
const alert = {
|
|
id: config.id,
|
|
title: config.name,
|
|
description: config.description,
|
|
city,
|
|
contradictionIndex: Math.round(index),
|
|
level: classification.level,
|
|
color: classification.color,
|
|
action: classification.action,
|
|
scores: {
|
|
[config.layers[0]]: score1,
|
|
[config.layers[1]]: score2,
|
|
difference: diff,
|
|
},
|
|
};
|
|
|
|
await sendAlert(alert);
|
|
alerts.push(alert);
|
|
}
|
|
}
|
|
}
|
|
|
|
return alerts;
|
|
}
|
|
|
|
/**
|
|
* Hämta aktiva varningar
|
|
*/
|
|
async function getActiveAlerts(city = null, limit = 50) {
|
|
const alerts = await redis.lrange('quixzoom:alerts', 0, limit - 1);
|
|
|
|
return alerts
|
|
.map(a => JSON.parse(a))
|
|
.filter(a => !city || a.city === city);
|
|
}
|
|
|
|
/**
|
|
* Hämta varningsstatistik
|
|
*/
|
|
async function getAlertStats(city = null) {
|
|
const alerts = await getActiveAlerts(city, 1000);
|
|
|
|
const stats = {
|
|
total: alerts.length,
|
|
byLevel: {},
|
|
byType: {},
|
|
byCity: {},
|
|
timeline: {},
|
|
};
|
|
|
|
for (const alert of alerts) {
|
|
// Per nivå
|
|
stats.byLevel[alert.level] = (stats.byLevel[alert.level] || 0) + 1;
|
|
|
|
// Per typ
|
|
stats.byType[alert.id] = (stats.byType[alert.id] || 0) + 1;
|
|
|
|
// Per stad
|
|
stats.byCity[alert.city] = (stats.byCity[alert.city] || 0) + 1;
|
|
|
|
// Timeline (per dag)
|
|
const date = alert.timestamp.split('T')[0];
|
|
stats.timeline[date] = (stats.timeline[date] || 0) + 1;
|
|
}
|
|
|
|
return stats;
|
|
}
|
|
|
|
/**
|
|
* Rensa gamla varningar
|
|
*/
|
|
async function cleanupOldAlerts(days = 30) {
|
|
const cutoff = new Date();
|
|
cutoff.setDate(cutoff.getDate() - days);
|
|
|
|
const alerts = await redis.lrange('quixzoom:alerts', 0, -1);
|
|
let removed = 0;
|
|
|
|
for (let i = alerts.length - 1; i >= 0; i--) {
|
|
const alert = JSON.parse(alerts[i]);
|
|
if (new Date(alert.timestamp) < cutoff) {
|
|
await redis.lrem('quixzoom:alerts', 0, alerts[i]);
|
|
removed++;
|
|
}
|
|
}
|
|
|
|
console.log(`[ALERT] Cleaned up ${removed} old alerts`);
|
|
return removed;
|
|
}
|
|
|
|
/**
|
|
* Exempel på användning
|
|
*/
|
|
async function example() {
|
|
// Bangkok scores efter 5 videor
|
|
const bangkokScores = {
|
|
commercial_vitality: 85,
|
|
infrastructure: 58,
|
|
economic: 78,
|
|
institutional: 61,
|
|
physical: 62,
|
|
operational: 84,
|
|
investment_confidence: 71,
|
|
growth: 82,
|
|
};
|
|
|
|
const alerts = await monitorContradictions('Bangkok', bangkokScores);
|
|
console.log(`[ALERT] Generated ${alerts.length} alerts`);
|
|
|
|
// Hämta aktiva varningar
|
|
const active = await getActiveAlerts('Bangkok');
|
|
console.log(`[ALERT] Active alerts: ${active.length}`);
|
|
|
|
// Statistik
|
|
const stats = await getAlertStats('Bangkok');
|
|
console.log('[ALERT] Stats:', stats);
|
|
}
|
|
|
|
// Kör exempel om filen körs direkt
|
|
if (require.main === module) {
|
|
example().catch(console.error);
|
|
}
|
|
|
|
module.exports = {
|
|
calculateContradiction,
|
|
classifyContradiction,
|
|
monitorContradictions,
|
|
getActiveAlerts,
|
|
getAlertStats,
|
|
cleanupOldAlerts,
|
|
ALERT_CONFIG,
|
|
};
|