janus-scanner-pro / script.js
hts-ai's picture
corrige
acf159f verified
document.addEventListener('DOMContentLoaded', function() {
// Initialize Feather icons
feather.replace();
// DOM Elements
const analyzeBtn = document.getElementById('analyzeBtn');
const analyzeBilanNormalBtn = document.getElementById('analyzeBilanNormalBtn');
const analyzeBilanDiversBtn = document.getElementById('analyzeBilanDiversBtn');
const fecFile = document.getElementById('fecFile');
const bilanNormalFile = document.getElementById('bilanNormalFile');
const bilanDiversFile = document.getElementById('bilanDiversFile');
const riskBar = document.getElementById('riskBar');
const riskScore = document.getElementById('riskScore');
const fecAnomalyCount = document.getElementById('fecAnomalyCount');
const bilanNormalAnomalyCount = document.getElementById('bilanNormalAnomalyCount');
const diversAccountCount = document.getElementById('diversAccountCount');
const locationResults = document.getElementById('locationResults');
const bilanNormalResults = document.getElementById('bilanNormalResults');
const bilanDiversResults = document.getElementById('bilanDiversResults');
let currentFecFile = null;
let currentBilanNormalFile = null;
let currentBilanDiversFile = null;
let scanHistoryData = JSON.parse(localStorage.getItem('scanHistory')) || [];
// Initialize scan history
renderScanHistory();
// Expose global functions for full detection page
// Global API
window.JanusFraudDetector = {
analyzeCryptoTransactions: analyzeCryptoTransactions,
detectOffshoreEntities: detectOffshoreEntities,
performBilanNormalAnalysis: performBilanNormalAnalysis,
performBilanDiversAnalysis: performBilanDiversAnalysis,
processExcelFile: processExcelFile,
processPDFFile: processPDFFile,
extractAmount: extractAmount,
extractAccountNumber: extractAccountNumber,
extractAccountName: extractAccountName,
calculateFinancialRatios: calculateFinancialRatios,
performFraudAnalysis: performFraudAnalysis,
performDiversAccountAnalysis: performDiversAccountAnalysis
};
// File upload handlers
fecFile.addEventListener('change', (e) => handleFileSelect(e, 'fec'));
bilanNormalFile.addEventListener('change', (e) => handleFileSelect(e, 'bilanNormal'));
bilanDiversFile.addEventListener('change', (e) => handleFileSelect(e, 'bilanDivers'));
// Drag and drop functionality
const dropZones = document.querySelectorAll('.border-dashed');
if (dropZones[0]) setupDropZone(dropZones[0], 'fec');
if (dropZones[1]) setupDropZone(dropZones[1], 'bilanNormal');
if (dropZones[2]) setupDropZone(dropZones[2], 'bilanDivers');
// Analyze button handlers
analyzeBtn.addEventListener('click', () => analyzeFile('fec'));
analyzeBilanNormalBtn.addEventListener('click', () => analyzeFile('bilanNormal'));
analyzeBilanDiversBtn.addEventListener('click', () => analyzeFile('bilanDivers'));
function setupDropZone(zone, type) {
zone.addEventListener('dragover', handleDragOver);
zone.addEventListener('dragleave', handleDragLeave);
zone.addEventListener('drop', (e) => handleDrop(e, type));
}
function handleFileSelect(event, type) {
const file = event.target.files[0];
if (file) {
if (type === 'fec') {
currentFecFile = file;
} else if (type === 'bilanNormal') {
currentBilanNormalFile = file;
} else if (type === 'bilanDivers') {
currentBilanDiversFile = file;
}
updateFileDisplay(file.name, type);
}
}
function handleDragOver(e) {
e.preventDefault();
e.stopPropagation();
e.currentTarget.classList.add('border-red-500');
}
function handleDragLeave(e) {
e.preventDefault();
e.stopPropagation();
e.currentTarget.classList.remove('border-red-500');
}
function handleDrop(e, type) {
e.preventDefault();
e.stopPropagation();
e.currentTarget.classList.remove('border-red-500');
const files = e.dataTransfer.files;
if (files.length > 0) {
if (type === 'fec') {
currentFecFile = files[0];
fecFile.files = files;
} else if (type === 'bilanNormal') {
currentBilanNormalFile = files[0];
bilanNormalFile.files = files;
} else if (type === 'bilanDivers') {
currentBilanDiversFile = files[0];
bilanDiversFile.files = files;
}
updateFileDisplay(files[0].name, type);
}
}
function updateFileDisplay(fileName, type) {
const dropZones = document.querySelectorAll('.border-dashed');
let dropZone;
if (type === 'fec') dropZone = dropZones[0];
else if (type === 'bilanNormal') dropZone = dropZones[1];
else if (type === 'bilanDivers') dropZone = dropZones[2];
if (dropZone) {
const icon = type === 'bilanDivers' ? 'alert-triangle' : 'file-text';
dropZone.innerHTML = `
<i data-feather="${icon}" class="mx-auto mb-4 text-red-500"></i>
<p class="text-green-400 mb-2">File Selected:</p>
<p class="text-white font-medium">${fileName}</p>
<p class="text-gray-400 text-sm mt-2">Ready for ${type.toUpperCase()} analysis</p>
`;
feather.replace();
}
}
async function analyzeFile(type) {
let file, btn;
if (type === 'fec') {
file = currentFecFile;
btn = analyzeBtn;
} else if (type === 'bilanNormal') {
file = currentBilanNormalFile;
btn = analyzeBilanNormalBtn;
} else if (type === 'bilanDivers') {
file = currentBilanDiversFile;
btn = analyzeBilanDiversBtn;
}
if (!file) {
alert(`Please select a ${type.toUpperCase()} file first`);
return;
}
btn.disabled = true;
btn.innerHTML = `<i data-feather="loader" class="animate-spin inline mr-2"></i>Analyzing ${type.toUpperCase()}...`;
feather.replace();
try {
let results;
if (type === 'fec') {
results = await performAnalysis(file);
displayFecResults(results);
} else if (type === 'bilanNormal') {
results = await performBilanNormalAnalysis(file);
displayBilanNormalResults(results);
} else if (type === 'bilanDivers') {
results = await performBilanDiversAnalysis(file);
displayBilanDiversResults(results);
}
// Combine and update overall stats
updateCombinedStats();
addToScanHistory({...results, type: type.toUpperCase()});
} catch (error) {
console.error(`${type} analysis failed:`, error);
alert(`${type.toUpperCase()} analysis failed: ` + error.message);
} finally {
btn.disabled = false;
btn.innerHTML = type === 'fec' ? '[LANCER ANALYSE FEC]' :
type === 'bilanNormal' ? '[LANCER ANALYSE NORMALE]' :
'[LANCER ANALYSE DIVERS]';
}
}
async function performBilanNormalAnalysis(file) {
// Analyse financière normale du bilan
await new Promise(resolve => setTimeout(resolve, 2000));
const fileExtension = file.name.split('.').pop().toLowerCase();
let data = [];
if (fileExtension === 'xlsx' || fileExtension === 'xls') {
data = await processExcelFile(file);
} else if (fileExtension === 'pdf') {
data = await processPDFFile(file);
} else {
throw new Error('Unsupported file format');
}
// Perform normal bilan analysis
const analysisResults = performNormalBilanAnalysis(data);
return {
fileName: file.name,
date: new Date().toISOString(),
entries: data.length,
normalAnomalies: analysisResults.anomalies,
financialRatios: analysisResults.ratios,
riskScore: analysisResults.riskScore,
summary: `Normal bilan analysis completed: Financial health assessment done`
};
}
async function performBilanDiversAnalysis(file) {
// Analyse spΓ©cialisΓ©e des comptes divers
await new Promise(resolve => setTimeout(resolve, 2500));
const fileExtension = file.name.split('.').pop().toLowerCase();
let data = [];
if (fileExtension === 'xlsx' || fileExtension === 'xls') {
data = await processExcelFile(file);
} else if (fileExtension === 'pdf') {
data = await processPDFFile(file);
} else {
throw new Error('Unsupported file format');
}
// Perform divers account analysis
const analysisResults = performDiversAccountAnalysis(data);
return {
fileName: file.name,
date: new Date().toISOString(),
entries: data.length,
diversAnomalies: analysisResults.anomalies,
diversAccounts: analysisResults.diversAccounts,
offshoreEntities: analysisResults.offshoreEntities,
riskScore: analysisResults.riskScore,
summary: `Divers analysis completed: ${analysisResults.diversAccounts.length} divers accounts detected`
};
}
function performNormalBilanAnalysis(data) {
const anomalies = [];
const ratios = calculateFinancialRatios(data);
// Analyse des ratios financiers
if (ratios.liquidite < 1.0) {
anomalies.push({
type: 'Poor Liquidity',
severity: 'HIGH',
description: `Liquidity ratio below 1.0: ${ratios.liquidite.toFixed(2)}`
});
}
if (ratios.solvabilite < 0.2) {
anomalies.push({
type: 'Low Solvency',
severity: 'MEDIUM',
description: `Solvency ratio below 20%: ${(ratios.solvabilite * 100).toFixed(1)}%`
});
}
if (ratios.endettement > 80) {
anomalies.push({
type: 'High Debt Ratio',
severity: 'HIGH',
description: `Debt ratio above 80%: ${ratios.endettement.toFixed(1)}%`
});
}
// Calcul du risque normal
const highRiskCount = anomalies.filter(a => a.severity === 'HIGH').length;
const mediumRiskCount = anomalies.filter(a => a.severity === 'MEDIUM').length;
const riskScore = Math.min(100, (highRiskCount * 40) + (mediumRiskCount * 20));
return {
anomalies,
ratios,
riskScore
};
}
function performDiversAccountAnalysis(data) {
const anomalies = [];
const diversAccounts = [];
const offshoreEntities = [];
const offshorePatterns = [
'panama', 'seychelles', 'cayman', 'bvi', 'bahamas', 'bermuda', 'marshall islands',
'luxembourg', 'singapore', 'hong kong', 'delaware', 'wyoming', 'nevis'
];
const diversAccounts = ['481', '486', '487', 'divers', 'suspens', 'provision'];
// Analyse des comptes divers suspects
data.forEach((entry, index) => {
const text = entry.text || JSON.stringify(entry);
const lowerText = text.toLowerCase();
// DΓ©tection des comptes divers
diversAccounts.forEach(account => {
if (lowerText.includes(account)) {
const amount = extractAmount(lowerText);
if (amount > 5000) {
diversAccounts.push({
accountNumber: account,
accountName: extractAccountName(text),
amount: amount,
severity: amount > 50000 ? 'HIGH' : amount > 10000 ? 'MEDIUM' : 'LOW',
description: `Suspicious ${account} account activity`,
line: entry.line || index
});
anomalies.push({
type: 'Suspicious Divers Account',
line: entry.line || index,
severity: amount > 50000 ? 'HIGH' : 'MEDIUM',
description: `Account ${account} with amount ${amount.toLocaleString()}`
});
}
}
});
// DΓ©tection d'entitΓ©s offshore
offshorePatterns.forEach(location => {
if (lowerText.includes(location)) {
offshoreEntities.push({
location: location,
severity: 'HIGH',
description: `Potential offshore entity in ${location.toUpperCase()}`,
line: entry.line || index
});
anomalies.push({
type: 'Offshore Entity',
line: entry.line || index,
severity: 'HIGH',
description: `Potential offshore entity in ${location.toUpperCase()}`
});
}
});
// Transactions en cash suspectes
if (lowerText.includes('cash') && extractAmount(lowerText) > 10000) {
anomalies.push({
type: 'Large Cash Transaction',
line: entry.line || index,
severity: 'HIGH',
description: 'Large cash payment detected'
});
}
});
// Score de risque divers
const highRiskCount = anomalies.filter(a => a.severity === 'HIGH').length;
const mediumRiskCount = anomalies.filter(a => a.severity === 'MEDIUM').length;
const diversRiskScore = Math.min(100, (diversAccounts.length * 15) + (offshoreEntities.length * 25) + (highRiskCount * 30) + (mediumRiskCount * 10));
return {
anomalies,
diversAccounts,
offshoreEntities,
riskScore: diversRiskScore
};
}
function extractAmount(text) {
const amountMatch = text.match(/(\d{1,3}(?:,\d{3})*(?:\.\d{2})?)/g);
if (amountMatch) {
return parseFloat(amountMatch[0].replace(/,/g, '')) || 0;
}
return 0;
}
function extractAccountNumber(text) {
const match = text.match(/(481|486|487|\d{3,6})/);
return match ? match[0] : 'N/A';
}
function extractAccountName(text) {
const match = text.match(/[A-Za-z\s]{5,}/);
return match ? match[0].trim() : 'Divers Account';
}
function calculateFinancialRatios(data) {
// Calcul simplifiΓ© des ratios financiers
const totalAssets = data.reduce((sum, entry) => {
const amount = extractAmount(JSON.stringify(entry));
return entry.text?.toLowerCase().includes('actif') ? sum + amount : sum;
}, 0);
const totalLiabilities = data.reduce((sum, entry) => {
const amount = extractAmount(JSON.stringify(entry));
return entry.text?.toLowerCase().includes('passif') ? sum + amount : sum;
}, 0);
const equity = Math.max(0, totalAssets - totalLiabilities);
return {
liquidite: totalAssets > 0 ? (totalAssets / Math.max(totalLiabilities, 1)) : 0,
solvabilite: totalAssets > 0 ? (equity / totalAssets) : 0,
endettement: totalAssets > 0 ? (totalLiabilities / totalAssets * 100) : 0
};
}
// Fonction pour analyser les transactions crypto
async function analyzeCryptoTransactions(walletAddress) {
try {
const response = await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=usd`);
const prices = await response.json();
let transactions = { result: [] };
if (walletAddress.startsWith('0x')) {
const etherscanResponse = await fetch(`https://api.etherscan.io/api?module=account&action=txlist&address=${walletAddress}&startblock=0&endblock=99999999&page=1&offset=10&sort=desc&apikey=YourApiKeyToken`);
transactions = await etherscanResponse.json();
}
const anomalies = [];
let totalValue = 0;
if (transactions.result && transactions.result.length > 0) {
transactions.result.forEach(tx => {
const value = parseInt(tx.value) / 1e18;
totalValue += value;
if (value > 50000) {
anomalies.push({
type: 'High Volume Transfer',
description: `Transaction > $50,000 detected: ${value} ETH`,
severity: 'HIGH'
});
}
});
}
return {
wallet: walletAddress,
riskScore: Math.min(100, anomalies.length * 20 + (totalValue > 100000 ? 30 : 0)),
anomalies: anomalies,
totalValue: totalValue,
prices: prices
};
} catch (error) {
console.error('Crypto analysis failed:', error);
return {
wallet: walletAddress,
riskScore: 0,
anomalies: [],
totalValue: 0,
prices: {}
};
}
}
// Fonction pour dΓ©tecter les structures offshore
function detectOffshoreEntities(textData) {
const offshoreJurisdictions = [
'Panama', 'Seychelles', 'Cayman Islands', 'British Virgin Islands', 'BVI',
'Bahamas', 'Bermuda', 'Marshall Islands', 'Luxembourg', 'Singapore',
'Hong Kong', 'Delaware', 'Wyoming', 'Nevis', 'Anguilla', 'Barbados'
];
const suspiciousPatterns = [
'nominee director', 'bearer shares', 'anonymous', 'shell company',
'trust services', 'virtual office', 'PO Box', 'registered agent'
];
const detections = [];
offshoreJurisdictions.forEach(jurisdiction => {
if (textData.toLowerCase().includes(jurisdiction.toLowerCase())) {
detections.push({
type: 'Offshore Jurisdiction',
entity: jurisdiction,
severity: 'HIGH'
});
}
});
suspiciousPatterns.forEach(pattern => {
if (textData.toLowerCase().includes(pattern)) {
detections.push({
type: 'Suspicious Pattern',
pattern: pattern,
severity: 'MEDIUM'
});
}
});
return detections;
}
async function performAnalysis(file) {
const fileExtension = file.name.split('.').pop().toLowerCase();
let data = [];
if (fileExtension === 'xlsx' || fileExtension === 'xls') {
data = await processExcelFile(file);
} else if (fileExtension === 'pdf') {
data = await processPDFFile(file);
} else {
throw new Error('Unsupported file format');
}
// Perform local analysis instead of server
const fraudAnalysis = performFraudAnalysis(data);
return {
fileName: file.name,
date: new Date().toISOString(),
entries: data.length,
anomalies: fraudAnalysis.anomalies,
riskScore: fraudAnalysis.riskScore,
locations: fraudAnalysis.locations,
suspiciousTransactions: fraudAnalysis.suspiciousTransactions,
summary: fraudAnalysis.anomalies.length > 0
? `Fraud analysis detected ${fraudAnalysis.anomalies.length} anomalies with risk score ${fraudAnalysis.riskScore}%`
: 'No significant fraud patterns detected'
};
}
async function processExcelFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function(e) {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
const jsonData = XLSX.utils.sheet_to_json(firstSheet);
resolve(jsonData);
} catch (error) {
reject(new Error('Failed to process Excel file'));
}
};
reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsArrayBuffer(file);
});
}
async function processPDFFile(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async function(e) {
try {
const pdfData = new Uint8Array(e.target.result);
const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise;
let fullText = '';
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const textContent = await page.getTextContent();
const pageText = textContent.items.map(item => item.str).join(' ');
fullText += pageText + '\n';
}
// Parse PDF text for financial data
const data = parseFinancialData(fullText);
resolve(data);
} catch (error) {
reject(new Error('Failed to process PDF file'));
}
};
reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsArrayBuffer(file);
});
}
function parseFinancialData(text) {
// Simple regex patterns to extract financial data
const patterns = [
{ regex: /(\d{1,3}(?:,\d{3})*(?:\.\d{2})?)/g, type: 'amount' },
/([A-Z]{2})\s+(\d{5})/g, // State and zip
/(\d{2}\/\d{2}\/\d{4})/g, // Dates
];
const data = [];
const lines = text.split('\n');
lines.forEach((line, index) => {
if (line.trim()) {
const amounts = [...line.matchAll(patterns[0])].map(m => m[1]);
const states = [...line.matchAll(patterns[1])].map(m => ({ state: m[1], zip: m[2] }));
if (amounts.length > 0 || states.length > 0) {
data.push({
line: index + 1,
text: line.trim(),
amounts: amounts,
locations: states,
raw: line
});
}
}
});
return data;
}
function performFraudAnalysis(data) {
const anomalies = [];
const locations = new Map();
let totalAmount = 0;
let suspiciousTransactions = [];
data.forEach((entry, index) => {
// Analyze amounts for anomalies
entry.amounts?.forEach(amount => {
const numAmount = parseFloat(amount.replace(/,/g, ''));
totalAmount += numAmount;
// Flag large transactions
if (numAmount > 10000) {
anomalies.push({
type: 'Large Transaction',
line: entry.line,
amount: amount,
severity: numAmount > 50000 ? 'HIGH' : 'MEDIUM'
});
}
// Flag round numbers (potential fraud indicator)
if (numAmount % 1000 === 0 && numAmount > 0) {
anomalies.push({
type: 'Round Number',
line: entry.line,
amount: amount,
severity: 'LOW'
});
}
});
// Track locations
entry.locations?.forEach(loc => {
const locationKey = `${loc.state}-${loc.zip}`;
if (!locations.has(locationKey)) {
locations.set(locationKey, { state: loc.state, zip: loc.zip, count: 0, amount: 0 });
}
const locData = locations.get(locationKey);
locData.count++;
locData.amount += entry.amounts?.reduce((sum, amt) => sum + parseFloat(amt.replace(/,/g, '') || 0), 0) || 0;
});
// Pattern analysis for suspicious behavior
if (entry.raw.toLowerCase().includes('cash') || entry.raw.toLowerCase().includes('cash')) {
anomalies.push({
type: 'Cash Transaction',
line: entry.line,
severity: 'MEDIUM',
description: 'Large cash transaction detected'
});
}
});
// Calculate risk score
const highRiskAnomalies = anomalies.filter(a => a.severity === 'HIGH').length;
const mediumRiskAnomalies = anomalies.filter(a => a.severity === 'MEDIUM').length;
const lowRiskAnomalies = anomalies.filter(a => a.severity === 'LOW').length;
const riskScore = Math.min(100, Math.round(
(highRiskAnomalies * 30 + mediumRiskAnomalies * 15 + lowRiskAnomalies * 5) /
Math.max(data.length / 10, 1)
));
// Flag suspicious location patterns
locations.forEach((locData, key) => {
if (locData.count > 5 && locData.amount > 100000) {
anomalies.push({
type: 'Suspicious Location Pattern',
severity: 'HIGH',
description: `High volume (${locData.count} transactions) in ${key}`
});
}
});
suspiciousTransactions = anomalies
.filter(a => a.severity === 'HIGH')
.map(a => ({ ...a, fileIndex: data.findIndex(d => d.line === a.line) }));
return {
anomalies,
riskScore,
locations: Array.from(locations.values()),
suspiciousTransactions
};
}
function displayFecResults(results) {
// Update risk bar
riskBar.style.width = results.riskScore + '%';
riskBar.classList.add('animate-risk');
riskScore.textContent = results.riskScore + '%';
// Update FEC-specific anomaly count
fecAnomalyCount.textContent = results.anomalies.length;
// Update location analysis
displayLocationResults(results.locations, results.suspiciousTransactions);
// Show success message
showNotification('FEC analysis completed successfully!', 'success');
}
function displayBilanNormalResults(results) {
// Update bilan normal anomaly count
bilanNormalAnomalyCount.textContent = results.normalAnomalies.length;
// Update bilan normal results
bilanNormalResults.innerHTML = `
<div class="space-y-3">
${results.normalAnomalies.length === 0 ? `
<div class="text-center py-4 text-green-500">
<i data-feather="check-circle" class="mx-auto mb-2"></i>
<p>Analyse normale: Aucun problème détecté</p>
</div>
` : `
${results.normalAnomalies.map(anomaly => `
<div class="bg-gray-700 rounded p-3 border ${anomaly.severity === 'HIGH' ? 'border-red-500' : 'border-yellow-500'}">
<div class="text-sm">
<span class="font-medium">${anomaly.type}</span>
<div class="text-gray-400 mt-1">${anomaly.description}</div>
</div>
</div>
`).join('')}
`}
</div>
<div class="mt-4 p-3 bg-blue-900 bg-opacity-30 border border-blue-500 rounded">
<p class="text-blue-300 font-semibold mb-2">Ratios Financiers:</p>
<div class="text-sm text-gray-300">
<p>LiquiditΓ©: ${results.financialRatios.liquidite?.toFixed(2) || 'N/A'}</p>
<p>SolvabilitΓ©: ${results.financialRatios.solvabilite?.toFixed(2) || 'N/A'}</p>
<p>Endettement: ${results.financialRatios.endettement?.toFixed(2) || 'N/A'}%</p>
</div>
</div>
`;
feather.replace();
showNotification('Analyse bilan normale terminΓ©e!', 'success');
}
function displayBilanDiversResults(results) {
// Update divers account count
diversAccountCount.textContent = results.diversAccounts.length;
// Update bilan divers results
const diversAccounts = results.diversAccounts || [];
const offshoreEntities = results.offshoreEntities || [];
bilanDiversResults.innerHTML = `
<div class="space-y-3">
${diversAccounts.length === 0 ? `
<div class="text-center py-4 text-green-500">
<i data-feather="check-circle" class="mx-auto mb-2"></i>
<p>Aucun compte divers suspect dΓ©tectΓ©</p>
</div>
` : `
${diversAccounts.slice(0, 5).map(account => `
<div class="bg-gray-700 rounded p-3 border ${account.severity === 'HIGH' ? 'border-red-500' : 'border-orange-500'}">
<div class="flex justify-between items-center">
<span class="font-medium">${account.accountNumber}</span>
<span class="text-orange-500 font-bold">${account.amount.toLocaleString()} €</span>
</div>
<div class="text-sm text-gray-400 mt-1">
${account.accountName} | Risque: ${account.severity}
</div>
</div>
`).join('')}
`}
${offshoreEntities.length > 0 ? `
<div class="mt-3 p-3 bg-red-900 bg-opacity-50 border border-red-500 rounded">
<p class="text-red-300 font-semibold mb-2">
<i data-feather="globe" class="inline mr-2"></i>
${offshoreEntities.length} EntitΓ©s Offshore DΓ©tectΓ©es
</p>
<div class="text-sm text-gray-400">
${offshoreEntities.map(entity => `${entity.location.toUpperCase()}`).join(', ')}
</div>
</div>
` : ''}
</div>
<div class="mt-4 p-3 bg-orange-900 bg-opacity-50 border border-orange-500 rounded">
<p class="text-orange-300 font-semibold mb-2">
<i data-feather="alert-triangle" class="inline mr-2"></i>
Score Risque Divers: ${results.riskScore}%
</p>
<p class="text-sm text-gray-400">Investigation approfondie recommandΓ©e</p>
</div>
`;
feather.replace();
showNotification('Analyse bilan divers terminΓ©e!', 'success');
}
function updateCombinedStats() {
// This function can be enhanced to combine both FEC and BILAN results
// For now, it ensures the UI stays synchronized
const fecAnomalies = parseInt(fecAnomalyCount.textContent) || 0;
const bilanNormalAnomalies = parseInt(bilanNormalAnomalyCount.textContent) || 0;
const diversAccounts = parseInt(diversAccountCount.textContent) || 0;
// Calculate combined risk score
const combinedRisk = Math.min(100, Math.round(
(fecAnomalies * 20 + bilanNormalAnomalies * 15 + diversAccounts * 25) /
Math.max(1, fecAnomalies + bilanNormalAnomalies + diversAccounts)
));
// Update main risk score
riskBar.style.width = combinedRisk + '%';
riskBar.classList.add('animate-risk');
riskScore.textContent = combinedRisk + '%';
}
function displayLocationResults(locations, suspiciousTransactions) {
if (locations.length === 0) {
locationResults.innerHTML = `
<div class="text-center py-8 text-gray-500">
<i data-feather="map-pin" class="mx-auto mb-2"></i>
<p>No location data found</p>
</div>
`;
} else {
locationResults.innerHTML = locations.slice(0, 5).map(loc => `
<div class="bg-gray-700 rounded p-3">
<div class="flex justify-between items-center">
<span class="font-medium">${loc.state} ${loc.zip}</span>
<span class="text-red-500 font-bold">${loc.count} txns</span>
</div>
<div class="text-sm text-gray-400 mt-1">
Total: ${loc.amount.toLocaleString()}
</div>
</div>
`).join('');
if (suspiciousTransactions.length > 0) {
locationResults.innerHTML += `
<div class="mt-4 p-3 bg-red-900 bg-opacity-50 border border-red-500 rounded">
<p class="text-red-300 font-semibold mb-2">
<i data-feather="alert-triangle" class="inline mr-2"></i>
${suspiciousTransactions.length} High-Risk Transactions
</p>
<p class="text-sm text-gray-400">Review flagged transactions immediately</p>
</div>
`;
}
}
feather.replace();
}
function addToScanHistory(results) {
scanHistoryData.unshift({
fileName: results.fileName,
date: results.date,
riskScore: results.riskScore,
status: results.riskScore > 70 ? 'HIGH RISK' : results.riskScore > 40 ? 'MEDIUM RISK' : 'LOW RISK'
});
// Keep only last 10 entries
scanHistoryData = scanHistoryData.slice(0, 10);
localStorage.setItem('scanHistory', JSON.stringify(scanHistoryData));
renderScanHistory();
}
function renderScanHistory() {
if (scanHistoryData.length === 0) {
scanHistory.innerHTML = `
<tr>
<td colspan="4" class="py-4 px-4 text-center text-gray-500">
No scans yet. Upload a file to get started.
</td>
</tr>
`;
} else {
scanHistory.innerHTML = scanHistoryData.map(scan => `
<tr class="border-t border-gray-700">
<td class="py-3 px-4">${scan.fileName}</td>
<td class="py-3 px-4">${new Date(scan.date).toLocaleDateString()}</td>
<td class="py-3 px-4">
<span class="font-bold ${scan.riskScore > 70 ? 'text-red-500' : scan.riskScore > 40 ? 'text-yellow-500' : 'text-green-500'}">
${scan.riskScore}%
</span>
</td>
<td class="py-3 px-4">
<span class="px-2 py-1 rounded text-xs font-medium ${
scan.riskScore > 70 ? 'bg-red-900 text-red-300' :
scan.riskScore > 40 ? 'bg-yellow-900 text-yellow-300' :
'bg-green-900 text-green-300'
}">
${scan.status}
</span>
</td>
</tr>
`).join('');
}
}
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 ${
type === 'success' ? 'bg-green-600' : 'bg-blue-600'
} text-white`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
// Initialize tooltips and interactive elements
initializeTooltips();
function initializeTooltips() {
// Add hover effects to cards
const cards = document.querySelectorAll('.bg-gray-800');
cards.forEach(card => {
card.addEventListener('mouseenter', function() {
this.classList.add('transform', 'scale-105', 'transition', 'duration-200');
});
card.addEventListener('mouseleave', function() {
this.classList.remove('transform', 'scale-105');
});
});
}
// Export des fonctions pour utilisaton globale
window.JanusFraudDetector = {
analyzeCryptoTransactions,
detectOffshoreEntities,
performBilanNormalAnalysis,
performBilanDiversAnalysis,
processExcelFile,
processPDFFile,
extractAmount,
extractAccountNumber,
extractAccountName,
calculateFinancialRatios,
performFraudAnalysis,
performDiversAccountAnalysis
};
});