export interface Transaction { id: string; timestamp: number; // Unix ms (entier) lat: number; lng: number; amount: number; // Ğ1 (pas en centimes) city: string; fromKey: string; // SS58 Ğ1v2 : préfixe "g1", ~50 chars toKey: string; } // French + European cities where Ğ1 is used const CITIES: { name: string; lat: number; lng: number; weight: number }[] = [ { name: 'Paris', lat: 48.8566, lng: 2.3522, weight: 12 }, { name: 'Lyon', lat: 45.7640, lng: 4.8357, weight: 9 }, { name: 'Bordeaux', lat: 44.8378, lng: -0.5792, weight: 8 }, { name: 'Toulouse', lat: 43.6047, lng: 1.4442, weight: 8 }, { name: 'Montpellier', lat: 43.6108, lng: 3.8767, weight: 7 }, { name: 'Nantes', lat: 47.2184, lng: -1.5536, weight: 6 }, { name: 'Rennes', lat: 48.1173, lng: -1.6778, weight: 6 }, { name: 'Grenoble', lat: 45.1885, lng: 5.7245, weight: 5 }, { name: 'Marseille', lat: 43.2965, lng: 5.3698, weight: 7 }, { name: 'Strasbourg', lat: 48.5734, lng: 7.7521, weight: 4 }, { name: 'Lille', lat: 50.6292, lng: 3.0573, weight: 4 }, { name: 'Rouen', lat: 49.4432, lng: 1.0993, weight: 3 }, { name: 'Clermont-Ferrand', lat: 45.7772, lng: 3.0870, weight: 4 }, { name: 'Tours', lat: 47.3941, lng: 0.6848, weight: 3 }, { name: 'Poitiers', lat: 46.5802, lng: 0.3404, weight: 3 }, { name: 'Besançon', lat: 47.2378, lng: 6.0241, weight: 3 }, { name: 'Caen', lat: 49.1829, lng: -0.3707, weight: 2 }, { name: 'Nice', lat: 43.7102, lng: 7.2620, weight: 4 }, { name: 'Barcelone', lat: 41.3851, lng: 2.1734, weight: 3 }, { name: 'Bruxelles', lat: 50.8503, lng: 4.3517, weight: 3 }, { name: 'Genève', lat: 46.2044, lng: 6.1432, weight: 2 }, { name: 'Saint-Étienne', lat: 45.4397, lng: 4.3872, weight: 3 }, { name: 'Dijon', lat: 47.3220, lng: 5.0415, weight: 3 }, { name: 'Angers', lat: 47.4784, lng: -0.5632, weight: 2 }, ]; function randomBetween(min: number, max: number): number { return Math.random() * (max - min) + min; } function weightedRandom(items: T[]): T { const totalWeight = items.reduce((sum, item) => sum + item.weight, 0); let rand = Math.random() * totalWeight; for (const item of items) { rand -= item.weight; if (rand <= 0) return item; } return items[items.length - 1]; } // Génère une clé SS58 Ğ1v2 simulée : préfixe "g1" + 48 chars base58 function generateKey(): string { const chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; const suffix = Array.from({ length: 47 }, () => chars[Math.floor(Math.random() * chars.length)]).join(''); return 'g1' + suffix; } function generateTransactions(count: number, maxAgeMs: number): Transaction[] { const now = Date.now(); const transactions: Transaction[] = []; for (let i = 0; i < count; i++) { const city = weightedRandom(CITIES); const lat = city.lat + randomBetween(-0.08, 0.08); const lng = city.lng + randomBetween(-0.12, 0.12); const amount = Math.round(randomBetween(0.5, 150) * 100) / 100; transactions.push({ id: `tx-${i}-${Math.random().toString(36).slice(2)}`, timestamp: Math.floor(now - Math.random() * maxAgeMs), lat, lng, amount, city: city.name, fromKey: generateKey(), toKey: generateKey(), }); } return transactions.sort((a, b) => b.timestamp - a.timestamp); } const TRANSACTION_POOL = generateTransactions(2400, 30 * 24 * 60 * 60 * 1000); export function getTransactionsForPeriod(periodDays: number): Transaction[] { const cutoff = Date.now() - periodDays * 24 * 60 * 60 * 1000; return TRANSACTION_POOL.filter((tx) => tx.timestamp >= cutoff); } export function computeStats(transactions: Transaction[]) { const totalVolume = transactions.reduce((sum, tx) => sum + tx.amount, 0); const transactionCount = transactions.length; const cityVolumes: Record = {}; for (const tx of transactions) { if (!cityVolumes[tx.city]) { cityVolumes[tx.city] = { volume: 0, count: 0 }; } cityVolumes[tx.city].volume += tx.amount; cityVolumes[tx.city].count += 1; } const topCities = Object.entries(cityVolumes) .sort((a, b) => b[1].volume - a[1].volume) .slice(0, 3) .map(([name, data]) => ({ name, ...data })); return { totalVolume, transactionCount, topCities }; } export type { };