feat: nature des échanges — catégorisation et détail des commentaires de transactions
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Nouveau commentParser.ts : ~80 règles regex multilingues, 11 catégories - SubsquidAdapter : fetch du champ comment.remark depuis SubSquid - Transaction et TransactionArc : champs comment et category - StatsPanel : section Nature des échanges avec barres cliquables (détail inline) - FlowMap : tooltip au survol des arcs avec répartition catégories + commentaires - InfoPanel mis à jour Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+25
-8
@@ -1,4 +1,6 @@
|
||||
import type { Transaction } from './mockData';
|
||||
import type { TxCategory } from './commentParser';
|
||||
import { aggregateCategories } from './commentParser';
|
||||
|
||||
export interface TransactionArc {
|
||||
id: string;
|
||||
@@ -14,6 +16,8 @@ export interface TransactionArc {
|
||||
toCity: string;
|
||||
toCountry: string;
|
||||
toKey: string;
|
||||
comment: string | null;
|
||||
category: TxCategory;
|
||||
}
|
||||
|
||||
/** Corridor agrégé par paire de villes (fromCity → toCity). */
|
||||
@@ -28,6 +32,8 @@ export interface Corridor {
|
||||
toCountry: string;
|
||||
totalVolume: number;
|
||||
count: number;
|
||||
categories: { category: TxCategory; count: number; volume: number }[];
|
||||
comments: string[]; // échantillon de commentaires bruts (max 5, non nuls)
|
||||
}
|
||||
|
||||
export interface FlowStats {
|
||||
@@ -40,21 +46,30 @@ export interface FlowStats {
|
||||
|
||||
/** Agrège les arcs individuels en corridors ville→ville, triés par volume. */
|
||||
export function buildCorridors(arcs: TransactionArc[]): Corridor[] {
|
||||
const map = new Map<string, Corridor>();
|
||||
const map = new Map<string, { corridor: Omit<Corridor, 'categories' | 'comments'>; items: TransactionArc[] }>();
|
||||
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,
|
||||
corridor: {
|
||||
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,
|
||||
},
|
||||
items: [],
|
||||
});
|
||||
}
|
||||
const c = map.get(key)!;
|
||||
c.totalVolume += arc.amount;
|
||||
c.count++;
|
||||
const entry = map.get(key)!;
|
||||
entry.corridor.totalVolume += arc.amount;
|
||||
entry.corridor.count++;
|
||||
entry.items.push(arc);
|
||||
}
|
||||
return [...map.values()].sort((a, b) => b.totalVolume - a.totalVolume);
|
||||
|
||||
return [...map.values()].map(({ corridor, items }) => ({
|
||||
...corridor,
|
||||
categories: aggregateCategories(items.map((a) => ({ category: a.category, amount: a.amount }))),
|
||||
comments: items.map((a) => a.comment).filter((c): c is string => !!c).slice(0, 5),
|
||||
})).sort((a, b) => b.totalVolume - a.totalVolume);
|
||||
}
|
||||
|
||||
export function computeFlowStats(arcs: TransactionArc[]): FlowStats {
|
||||
@@ -114,6 +129,8 @@ export function buildMockArcs(transactions: Transaction[]): TransactionArc[] {
|
||||
toLat: to.lat, toLng: to.lng,
|
||||
toCity: to.city, toCountry: to.countryCode,
|
||||
toKey: to.toKey,
|
||||
comment: from.comment,
|
||||
category: from.category,
|
||||
});
|
||||
}
|
||||
return arcs;
|
||||
|
||||
Reference in New Issue
Block a user