import type { Transaction } from './mockData'; export interface TransactionArc { id: string; timestamp: number; // Unix ms amount: number; // Ğ1 fromLat: number; fromLng: number; fromCity: string; fromCountry: string; fromKey: string; toLat: number; toLng: number; toCity: string; toCountry: string; toKey: string; } /** Corridor agrégé par paire de villes (fromCity → toCity). */ export interface Corridor { fromCity: string; fromLat: number; fromLng: number; fromCountry: string; toCity: string; toLat: number; toLng: number; toCountry: string; totalVolume: number; count: number; } export interface FlowStats { totalVolume: number; arcCount: number; topEmitters: { city: string; volume: number; count: number; countryCode: string }[]; topReceivers: { city: string; volume: number; count: number; countryCode: string }[]; netBalance: { city: string; net: number; countryCode: string }[]; } /** Agrège les arcs individuels en corridors ville→ville, triés par volume. */ export function buildCorridors(arcs: TransactionArc[]): Corridor[] { const map = new Map(); for (const arc of arcs) { const key = `${arc.fromCity}||${arc.toCity}`; if (!map.has(key)) { map.set(key, { fromCity: arc.fromCity, fromLat: arc.fromLat, fromLng: arc.fromLng, fromCountry: arc.fromCountry, toCity: arc.toCity, toLat: arc.toLat, toLng: arc.toLng, toCountry: arc.toCountry, totalVolume: 0, count: 0, }); } const c = map.get(key)!; c.totalVolume += arc.amount; c.count++; } return [...map.values()].sort((a, b) => b.totalVolume - a.totalVolume); } export function computeFlowStats(arcs: TransactionArc[]): FlowStats { const emitters = new Map(); const receivers = new Map(); for (const arc of arcs) { if (!emitters.has(arc.fromCity)) emitters.set(arc.fromCity, { volume: 0, count: 0, country: arc.fromCountry }); if (!receivers.has(arc.toCity)) receivers.set(arc.toCity, { volume: 0, count: 0, country: arc.toCountry }); emitters.get(arc.fromCity)!.volume += arc.amount; emitters.get(arc.fromCity)!.count++; receivers.get(arc.toCity)!.volume += arc.amount; receivers.get(arc.toCity)!.count++; } const allCities = new Set([...emitters.keys(), ...receivers.keys()]); const netBalance = [...allCities].map(city => ({ city, net: (receivers.get(city)?.volume ?? 0) - (emitters.get(city)?.volume ?? 0), countryCode: emitters.get(city)?.country ?? receivers.get(city)?.country ?? '', })).sort((a, b) => Math.abs(b.net) - Math.abs(a.net)).slice(0, 5); return { totalVolume: arcs.reduce((s, a) => s + a.amount, 0), arcCount: arcs.length, topEmitters: [...emitters.entries()].sort((a, b) => b[1].volume - a[1].volume).slice(0, 3) .map(([city, d]) => ({ city, volume: d.volume, count: d.count, countryCode: d.country })), topReceivers: [...receivers.entries()].sort((a, b) => b[1].volume - a[1].volume).slice(0, 3) .map(([city, d]) => ({ city, volume: d.volume, count: d.count, countryCode: d.country })), netBalance, }; } /** * Génère des arcs mock : chaque transaction devient un arc dont le destinataire * est une transaction aléatoire d'une ville différente. */ export function buildMockArcs(transactions: Transaction[]): TransactionArc[] { if (transactions.length < 2) return []; const arcs: TransactionArc[] = []; for (let i = 0; i < transactions.length; i++) { if (Math.random() > 0.55) continue; // ~55 % de couverture const from = transactions[i]; let toIdx = Math.floor(Math.random() * transactions.length); for (let tries = 0; tries < 8 && transactions[toIdx].city === from.city; tries++) { toIdx = Math.floor(Math.random() * transactions.length); } const to = transactions[toIdx]; if (to.city === from.city) continue; arcs.push({ id: `${from.id}-arc`, timestamp: from.timestamp, amount: from.amount, fromLat: from.lat, fromLng: from.lng, fromCity: from.city, fromCountry: from.countryCode, fromKey: from.fromKey, toLat: to.lat, toLng: to.lng, toCity: to.city, toCountry: to.countryCode, toKey: to.toKey, }); } return arcs; }