pnpfucius 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,198 @@
1
+ // Dashboard data aggregation for market analytics
2
+ // Collects and formats metrics for display and API endpoints
3
+
4
+ import { agentEvents, AgentEvents } from '../events/emitter.js';
5
+
6
+ export class DashboardAggregator {
7
+ constructor(store) {
8
+ this.store = store;
9
+ this.cache = null;
10
+ this.cacheExpiry = 60000; // 1 minute cache
11
+ this.lastUpdate = 0;
12
+ }
13
+
14
+ async getOverview() {
15
+ // Use cache if fresh
16
+ if (this.cache && Date.now() - this.lastUpdate < this.cacheExpiry) {
17
+ return this.cache;
18
+ }
19
+
20
+ const stats = await this.store.getStats();
21
+ const recent = await this.store.getAllMarkets({ limit: 10 });
22
+ const performance = await this.store.getPerformanceMetrics();
23
+
24
+ const overview = {
25
+ summary: {
26
+ totalMarkets: stats.total,
27
+ activeMarkets: stats.active,
28
+ resolvedMarkets: stats.resolved,
29
+ cancelledMarkets: stats.cancelled,
30
+ recentWeek: stats.recentCount
31
+ },
32
+ categoryBreakdown: stats.byCategory.map(c => ({
33
+ category: c.category,
34
+ key: c.category_key,
35
+ count: c.count,
36
+ percentage: Math.round((c.count / stats.total) * 100)
37
+ })),
38
+ performance: {
39
+ totalVolume: performance.totalVolume,
40
+ averageDuration: performance.averageDuration,
41
+ resolutionRate: performance.resolutionRate,
42
+ resolvedCount: performance.marketCount
43
+ },
44
+ recentMarkets: recent.map(m => ({
45
+ address: m.address,
46
+ question: m.question,
47
+ category: m.category,
48
+ status: m.status,
49
+ createdAt: m.creationTime,
50
+ endTime: m.endTime
51
+ })),
52
+ lastUpdated: Date.now()
53
+ };
54
+
55
+ // Update cache
56
+ this.cache = overview;
57
+ this.lastUpdate = Date.now();
58
+
59
+ agentEvents.emitTyped(AgentEvents.STATS_UPDATED, { overview });
60
+
61
+ return overview;
62
+ }
63
+
64
+ async getPerformanceMetrics() {
65
+ return this.store.getPerformanceMetrics();
66
+ }
67
+
68
+ async getCategoryStats() {
69
+ const stats = await this.store.getStats();
70
+ return stats.byCategory;
71
+ }
72
+
73
+ async getTimeSeriesData(period = '7d') {
74
+ const now = Date.now();
75
+ let since;
76
+
77
+ switch (period) {
78
+ case '24h':
79
+ since = now - 24 * 60 * 60 * 1000;
80
+ break;
81
+ case '7d':
82
+ since = now - 7 * 24 * 60 * 60 * 1000;
83
+ break;
84
+ case '30d':
85
+ since = now - 30 * 24 * 60 * 60 * 1000;
86
+ break;
87
+ default:
88
+ since = now - 7 * 24 * 60 * 60 * 1000;
89
+ }
90
+
91
+ const markets = await this.store.getAllMarkets({ since });
92
+
93
+ // Group by day
94
+ const dailyData = {};
95
+
96
+ for (const market of markets) {
97
+ const day = new Date(market.creationTime).toISOString().split('T')[0];
98
+
99
+ if (!dailyData[day]) {
100
+ dailyData[day] = {
101
+ date: day,
102
+ created: 0,
103
+ volume: 0n
104
+ };
105
+ }
106
+
107
+ dailyData[day].created++;
108
+
109
+ if (market.volume) {
110
+ dailyData[day].volume += BigInt(market.volume);
111
+ }
112
+ }
113
+
114
+ // Convert to array and sort
115
+ return Object.values(dailyData)
116
+ .map(d => ({
117
+ ...d,
118
+ volume: d.volume.toString()
119
+ }))
120
+ .sort((a, b) => a.date.localeCompare(b.date));
121
+ }
122
+
123
+ async getActiveMarketsSummary() {
124
+ const active = await this.store.getAllMarkets({ status: 'active' });
125
+
126
+ // Sort by end time (closest to expiry first)
127
+ active.sort((a, b) => a.endTime - b.endTime);
128
+
129
+ return active.map(m => {
130
+ const timeLeft = m.endTime - Date.now();
131
+ const daysLeft = Math.ceil(timeLeft / (24 * 60 * 60 * 1000));
132
+
133
+ return {
134
+ address: m.address,
135
+ question: m.question,
136
+ category: m.category,
137
+ daysLeft: Math.max(0, daysLeft),
138
+ endTime: m.endTime,
139
+ status: daysLeft <= 0 ? 'expired' : daysLeft <= 7 ? 'expiring_soon' : 'active'
140
+ };
141
+ });
142
+ }
143
+
144
+ async getResolutionPendingMarkets() {
145
+ const active = await this.store.getAllMarkets({ status: 'active' });
146
+ const now = Date.now();
147
+
148
+ // Filter markets past their end time
149
+ return active
150
+ .filter(m => m.endTime < now)
151
+ .map(m => ({
152
+ address: m.address,
153
+ question: m.question,
154
+ endedAt: new Date(m.endTime).toISOString(),
155
+ daysPastEnd: Math.floor((now - m.endTime) / (24 * 60 * 60 * 1000))
156
+ }));
157
+ }
158
+
159
+ // Invalidate cache
160
+ invalidateCache() {
161
+ this.cache = null;
162
+ this.lastUpdate = 0;
163
+ }
164
+ }
165
+
166
+ export function createAggregator(store) {
167
+ return new DashboardAggregator(store);
168
+ }
169
+
170
+ // Format large numbers for display
171
+ export function formatNumber(num) {
172
+ const n = BigInt(num);
173
+
174
+ if (n >= 1000000000n) {
175
+ return `${Number(n / 1000000000n).toFixed(1)}B`;
176
+ }
177
+ if (n >= 1000000n) {
178
+ return `${Number(n / 1000000n).toFixed(1)}M`;
179
+ }
180
+ if (n >= 1000n) {
181
+ return `${Number(n / 1000n).toFixed(1)}K`;
182
+ }
183
+
184
+ return n.toString();
185
+ }
186
+
187
+ // Format time duration
188
+ export function formatDuration(ms) {
189
+ const seconds = Math.floor(ms / 1000);
190
+ const minutes = Math.floor(seconds / 60);
191
+ const hours = Math.floor(minutes / 60);
192
+ const days = Math.floor(hours / 24);
193
+
194
+ if (days > 0) return `${days}d`;
195
+ if (hours > 0) return `${hours}h`;
196
+ if (minutes > 0) return `${minutes}m`;
197
+ return `${seconds}s`;
198
+ }