Files
g1flux/src/services/DataService.ts
syoul 31f8db6625 chore: remove debug logs from DataService
Pipeline confirmed working (17826 keyMap, ~350 geolocalized).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 18:13:56 +01:00

135 lines
4.4 KiB
TypeScript

/**
* DataService — couche d'abstraction entre l'UI et les sources de données.
*
* Mode mock (USE_LIVE_API = false) : données simulées, aucun appel réseau.
* Mode live (USE_LIVE_API = true) : données réelles Ğ1v2.
* - Transactions : Subsquid indexer https://squidv2s.syoul.fr/v1/graphql
* - Géolocalisation : Cesium+ https://g1.data.e-is.pro
* → lookup par clé Duniter (_id) via conversion SS58 → base58 Ed25519
* → membres migrés v1→v2 : clé genesis (previousId) = _id Cesium+
* → carte d'identité (keyMap) mise en cache 10 min, 1 seule requête
*
* Pour activer : définir VITE_USE_LIVE_API=true dans .env.local
*/
import { fetchTransfers, buildIdentityKeyMap } from './adapters/SubsquidAdapter';
import { resolveGeoByKeys } from './adapters/CesiumAdapter';
import {
getTransactionsForPeriod,
computeStats,
type Transaction,
} from '../data/mockData';
const USE_LIVE_API = import.meta.env.VITE_USE_LIVE_API === 'true';
// Cache de la carte identité SS58→DuniterKey, valide 10 minutes
let keyMapCache: { map: Map<string, string>; expiresAt: number } | null = null;
async function getIdentityKeyMap(): Promise<Map<string, string>> {
if (keyMapCache && Date.now() < keyMapCache.expiresAt) return keyMapCache.map;
const map = await buildIdentityKeyMap();
keyMapCache = { map, expiresAt: Date.now() + 10 * 60 * 1000 };
return map;
}
async function fetchLiveTransactions(periodDays: number): Promise<{
geolocated: Transaction[];
totalCount: number;
totalVolume: number;
}> {
const rawTransfers = await fetchTransfers(periodDays);
if (rawTransfers.length === 0) return { geolocated: [], totalCount: 0, totalVolume: 0 };
const totalCount = rawTransfers.length;
const totalVolume = rawTransfers.reduce((s, t) => s + t.amount, 0);
// Carte SS58 courant → clé Duniter (= _id Cesium+)
let keyMap = new Map<string, string>();
try {
keyMap = await getIdentityKeyMap();
} catch (err) {
console.warn('Identity key map indisponible :', err);
}
// Clés Duniter uniques des émetteurs
const duniterKeys = [...new Set(
rawTransfers.map((t) => keyMap.get(t.fromId)).filter(Boolean) as string[]
)];
// Résolution géo par clé Duniter (_id Cesium+)
let geoMap = new Map<string, { lat: number; lng: number; city: string }>();
try {
const profiles = await resolveGeoByKeys(duniterKeys);
for (const [key, p] of profiles) {
geoMap.set(key, { lat: p.lat, lng: p.lng, city: p.city });
}
} catch (err) {
console.warn('Cesium+ indisponible :', err);
}
// Seules les transactions avec un profil géo entrent dans le heatmap
const geolocated: Transaction[] = [];
for (const t of rawTransfers) {
const duniterKey = keyMap.get(t.fromId);
if (!duniterKey) continue;
const geo = geoMap.get(duniterKey);
if (!geo) continue;
geolocated.push({
id: t.id,
timestamp: t.timestamp,
lat: geo.lat,
lng: geo.lng,
amount: t.amount,
city: geo.city,
fromKey: t.fromId,
toKey: t.toId,
});
}
return { geolocated, totalCount, totalVolume };
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export interface PeriodStats {
totalVolume: number;
transactionCount: number; // total blockchain (y compris non-géolocalisés)
geoCount: number; // transactions visibles sur la carte
topCities: { name: string; volume: number; count: number }[];
}
export interface DataResult {
transactions: Transaction[]; // uniquement géolocalisées → heatmap
stats: PeriodStats;
source: 'live' | 'mock';
}
export async function fetchData(periodDays: number): Promise<DataResult> {
if (!USE_LIVE_API) {
await new Promise((r) => setTimeout(r, 80));
const transactions = getTransactionsForPeriod(periodDays);
const base = computeStats(transactions);
return {
transactions,
stats: { ...base, geoCount: transactions.length },
source: 'mock',
};
}
const { geolocated, totalCount, totalVolume } = await fetchLiveTransactions(periodDays);
const base = computeStats(geolocated);
return {
transactions: geolocated,
stats: {
totalVolume,
transactionCount: totalCount,
geoCount: geolocated.length,
topCities: base.topCities,
},
source: 'live',
};
}