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 = `
File Selected:
${fileName}
Ready for ${type.toUpperCase()} analysis
`; 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 = `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 = `Analyse normale: Aucun problème détecté
Ratios Financiers:
Liquidité: ${results.financialRatios.liquidite?.toFixed(2) || 'N/A'}
Solvabilité: ${results.financialRatios.solvabilite?.toFixed(2) || 'N/A'}
Endettement: ${results.financialRatios.endettement?.toFixed(2) || 'N/A'}%
Aucun compte divers suspect détecté
${offshoreEntities.length} Entités Offshore Détectées
Score Risque Divers: ${results.riskScore}%
Investigation approfondie recommandée
No location data found
${suspiciousTransactions.length} High-Risk Transactions
Review flagged transactions immediately