@the-situation/indexer 0.17.1 → 0.18.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.
- package/dist/aggregator/cohort.d.ts +30 -0
- package/dist/aggregator/cohort.d.ts.map +1 -0
- package/dist/aggregator/cohort.js +153 -0
- package/dist/aggregator/cohort.js.map +1 -0
- package/dist/aggregator/daily.d.ts +71 -0
- package/dist/aggregator/daily.d.ts.map +1 -0
- package/dist/aggregator/daily.js +249 -0
- package/dist/aggregator/daily.js.map +1 -0
- package/dist/aggregator/domains.d.ts +20 -0
- package/dist/aggregator/domains.d.ts.map +1 -0
- package/dist/aggregator/domains.js +38 -0
- package/dist/aggregator/domains.js.map +1 -0
- package/dist/aggregator/index.d.ts +31 -0
- package/dist/aggregator/index.d.ts.map +1 -0
- package/dist/aggregator/index.js +100 -0
- package/dist/aggregator/index.js.map +1 -0
- package/dist/aggregator/lifetime.d.ts +52 -0
- package/dist/aggregator/lifetime.d.ts.map +1 -0
- package/dist/aggregator/lifetime.js +222 -0
- package/dist/aggregator/lifetime.js.map +1 -0
- package/dist/aggregator/roi.d.ts +42 -0
- package/dist/aggregator/roi.d.ts.map +1 -0
- package/dist/aggregator/roi.js +153 -0
- package/dist/aggregator/roi.js.map +1 -0
- package/dist/aggregator/sources.d.ts +59 -0
- package/dist/aggregator/sources.d.ts.map +1 -0
- package/dist/aggregator/sources.js +53 -0
- package/dist/aggregator/sources.js.map +1 -0
- package/dist/aggregator/writers.d.ts +22 -0
- package/dist/aggregator/writers.d.ts.map +1 -0
- package/dist/aggregator/writers.js +147 -0
- package/dist/aggregator/writers.js.map +1 -0
- package/dist/api/app.d.ts +177 -13
- package/dist/api/app.d.ts.map +1 -1
- package/dist/api/app.js +4 -3
- package/dist/api/app.js.map +1 -1
- package/dist/api/routes/admin-subscriptions.d.ts +10 -10
- package/dist/api/routes/analytics.d.ts +205 -0
- package/dist/api/routes/analytics.d.ts.map +1 -0
- package/dist/api/routes/analytics.js +122 -0
- package/dist/api/routes/analytics.js.map +1 -0
- package/dist/api/routes/index.d.ts +1 -0
- package/dist/api/routes/index.d.ts.map +1 -1
- package/dist/api/routes/index.js +1 -0
- package/dist/api/routes/index.js.map +1 -1
- package/dist/api/routes/lp-history.d.ts +1 -1
- package/dist/api/routes/rankings.d.ts +17 -3
- package/dist/api/routes/rankings.d.ts.map +1 -1
- package/dist/api/routes/rankings.js +44 -5
- package/dist/api/routes/rankings.js.map +1 -1
- package/dist/api/routes/trader-stats.d.ts +11 -2
- package/dist/api/routes/trader-stats.d.ts.map +1 -1
- package/dist/api/routes/trader-stats.js +72 -2
- package/dist/api/routes/trader-stats.js.map +1 -1
- package/dist/client/IndexerClient.d.ts +30 -1
- package/dist/client/IndexerClient.d.ts.map +1 -1
- package/dist/client/IndexerClient.js.map +1 -1
- package/dist/client/IndexerClientLive.d.ts.map +1 -1
- package/dist/client/IndexerClientLive.js +50 -1
- package/dist/client/IndexerClientLive.js.map +1 -1
- package/dist/client/convenience.d.ts +9 -2
- package/dist/client/convenience.d.ts.map +1 -1
- package/dist/client/convenience.js +9 -1
- package/dist/client/convenience.js.map +1 -1
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/etl/event-indexer.d.ts +1 -1
- package/dist/etl/lp-position-refresher.d.ts +2 -1
- package/dist/etl/lp-position-refresher.d.ts.map +1 -1
- package/dist/etl/lp-position-refresher.js +24 -11
- package/dist/etl/lp-position-refresher.js.map +1 -1
- package/dist/etl/position-refresher.d.ts +1 -1
- package/dist/etl/state-refresher.d.ts +1 -1
- package/dist/index.js +54 -5
- package/dist/index.js.map +1 -1
- package/dist/layers/ChainReaderLive.d.ts.map +1 -1
- package/dist/layers/ChainReaderLive.js +15 -0
- package/dist/layers/ChainReaderLive.js.map +1 -1
- package/dist/services/ChainReader.d.ts +10 -0
- package/dist/services/ChainReader.d.ts.map +1 -1
- package/dist/services/ChainReader.js.map +1 -1
- package/dist/types/analytics.d.ts +194 -0
- package/dist/types/analytics.d.ts.map +1 -0
- package/dist/types/analytics.js +10 -0
- package/dist/types/analytics.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/warehouse/analytics-helpers.d.ts +36 -0
- package/dist/warehouse/analytics-helpers.d.ts.map +1 -0
- package/dist/warehouse/analytics-helpers.js +142 -0
- package/dist/warehouse/analytics-helpers.js.map +1 -0
- package/dist/warehouse/backfill.d.ts +26 -0
- package/dist/warehouse/backfill.d.ts.map +1 -0
- package/dist/warehouse/backfill.js +77 -0
- package/dist/warehouse/backfill.js.map +1 -0
- package/dist/warehouse/connection.d.ts +18 -0
- package/dist/warehouse/connection.d.ts.map +1 -0
- package/dist/warehouse/connection.js +24 -0
- package/dist/warehouse/connection.js.map +1 -0
- package/dist/warehouse/index.d.ts +14 -0
- package/dist/warehouse/index.d.ts.map +1 -0
- package/dist/warehouse/index.js +14 -0
- package/dist/warehouse/index.js.map +1 -0
- package/dist/warehouse/repositories/analytics.d.ts +61 -0
- package/dist/warehouse/repositories/analytics.d.ts.map +1 -0
- package/dist/warehouse/repositories/analytics.js +418 -0
- package/dist/warehouse/repositories/analytics.js.map +1 -0
- package/dist/warehouse/schema.d.ts +9 -0
- package/dist/warehouse/schema.d.ts.map +1 -0
- package/dist/warehouse/schema.js +219 -0
- package/dist/warehouse/schema.js.map +1 -0
- package/dist/warehouse/seed-domains.d.ts +29 -0
- package/dist/warehouse/seed-domains.d.ts.map +1 -0
- package/dist/warehouse/seed-domains.js +223 -0
- package/dist/warehouse/seed-domains.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { computeCohortHalfLife, dateToUnixSeconds, parseNumeric, shapeCohortRows, toDateString, } from '../analytics-helpers';
|
|
2
|
+
export class PgAnalyticsRepository {
|
|
3
|
+
sql;
|
|
4
|
+
constructor(sql) {
|
|
5
|
+
this.sql = sql;
|
|
6
|
+
}
|
|
7
|
+
async getOverview() {
|
|
8
|
+
const [totals] = (await this.sql `
|
|
9
|
+
SELECT
|
|
10
|
+
(SELECT COUNT(*) FROM trader_lifetime) AS traders,
|
|
11
|
+
(SELECT COUNT(*) FROM lp_lifetime) AS lps,
|
|
12
|
+
(SELECT COALESCE(SUM(total_trades), 0) FROM trader_lifetime) AS trades,
|
|
13
|
+
(SELECT COALESCE(SUM(total_volume), 0) FROM trader_lifetime) AS volume,
|
|
14
|
+
(SELECT COUNT(DISTINCT market_address) FROM daily_market_stats) AS active_markets
|
|
15
|
+
`);
|
|
16
|
+
const [posRoi] = (await this.sql `
|
|
17
|
+
SELECT total_users, positive_roi_pct
|
|
18
|
+
FROM roi_summary
|
|
19
|
+
WHERE domain_slug = 'all' AND entity_type = 'trader'
|
|
20
|
+
`);
|
|
21
|
+
const sevenDaysAgo = new Date(Date.now() - 7 * 86_400_000)
|
|
22
|
+
.toISOString()
|
|
23
|
+
.slice(0, 10);
|
|
24
|
+
const [newUsers] = (await this.sql `
|
|
25
|
+
SELECT
|
|
26
|
+
COALESCE(SUM(new_traders), 0) AS new_traders,
|
|
27
|
+
COALESCE(SUM(new_lps), 0) AS new_lps
|
|
28
|
+
FROM daily_platform_stats
|
|
29
|
+
WHERE date >= ${sevenDaysAgo}::date
|
|
30
|
+
`);
|
|
31
|
+
const top = (await this.sql `
|
|
32
|
+
SELECT
|
|
33
|
+
d.slug, d.name,
|
|
34
|
+
COALESCE(SUM(s.volume_collateral), 0) AS volume,
|
|
35
|
+
COALESCE(SUM(s.trade_count), 0) AS trades,
|
|
36
|
+
COALESCE(MAX(s.unique_traders), 0) AS unique_traders
|
|
37
|
+
FROM domains d
|
|
38
|
+
LEFT JOIN daily_domain_stats s ON s.domain_slug = d.slug
|
|
39
|
+
WHERE d.slug <> 'other' OR EXISTS (SELECT 1 FROM daily_domain_stats x WHERE x.domain_slug = 'other')
|
|
40
|
+
GROUP BY d.slug, d.name
|
|
41
|
+
ORDER BY volume DESC
|
|
42
|
+
LIMIT 10
|
|
43
|
+
`);
|
|
44
|
+
return {
|
|
45
|
+
totals: {
|
|
46
|
+
traders: totals?.traders ?? 0,
|
|
47
|
+
lps: totals?.lps ?? 0,
|
|
48
|
+
trades: parseNumeric(totals?.trades ?? 0) ?? 0,
|
|
49
|
+
volume: parseNumeric(totals?.volume ?? 0) ?? 0,
|
|
50
|
+
activeMarkets: totals?.active_markets ?? 0,
|
|
51
|
+
},
|
|
52
|
+
positiveRoiPct: parseNumeric(posRoi?.positive_roi_pct) ?? 0,
|
|
53
|
+
newTradersLast7d: parseNumeric(newUsers?.new_traders) ?? 0,
|
|
54
|
+
newLpsLast7d: parseNumeric(newUsers?.new_lps) ?? 0,
|
|
55
|
+
topDomainsByVolume: top.map((r) => ({
|
|
56
|
+
slug: r.slug,
|
|
57
|
+
name: r.name,
|
|
58
|
+
volume: parseNumeric(r.volume) ?? 0,
|
|
59
|
+
trades: parseNumeric(r.trades) ?? 0,
|
|
60
|
+
uniqueTraders: parseNumeric(r.unique_traders) ?? 0,
|
|
61
|
+
})),
|
|
62
|
+
computedAt: new Date().toISOString(),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async getDomains() {
|
|
66
|
+
const rows = (await this.sql `
|
|
67
|
+
SELECT
|
|
68
|
+
d.slug,
|
|
69
|
+
d.name,
|
|
70
|
+
d.description,
|
|
71
|
+
COALESCE(md.active_markets, 0) AS active_markets,
|
|
72
|
+
COALESCE(s.unique_traders, 0) AS unique_traders,
|
|
73
|
+
COALESCE(s.unique_lps, 0) AS unique_lps,
|
|
74
|
+
COALESCE(s.total_volume, 0) AS total_volume,
|
|
75
|
+
COALESCE(s.total_trades, 0) AS total_trades,
|
|
76
|
+
COALESCE(r.positive_roi_pct, 0) AS positive_roi_pct,
|
|
77
|
+
r.median_roi_pct
|
|
78
|
+
FROM domains d
|
|
79
|
+
LEFT JOIN (
|
|
80
|
+
SELECT domain_slug,
|
|
81
|
+
SUM(volume_collateral) AS total_volume,
|
|
82
|
+
SUM(trade_count) AS total_trades,
|
|
83
|
+
MAX(unique_traders) AS unique_traders,
|
|
84
|
+
MAX(unique_lps) AS unique_lps
|
|
85
|
+
FROM daily_domain_stats
|
|
86
|
+
GROUP BY domain_slug
|
|
87
|
+
) s ON s.domain_slug = d.slug
|
|
88
|
+
LEFT JOIN (
|
|
89
|
+
SELECT domain_slug, COUNT(DISTINCT market_address) AS active_markets
|
|
90
|
+
FROM market_domains
|
|
91
|
+
GROUP BY domain_slug
|
|
92
|
+
) md ON md.domain_slug = d.slug
|
|
93
|
+
LEFT JOIN roi_summary r ON r.domain_slug = d.slug AND r.entity_type = 'trader'
|
|
94
|
+
ORDER BY d.sort_order ASC, d.slug ASC
|
|
95
|
+
`);
|
|
96
|
+
const domains = rows.map((r) => ({
|
|
97
|
+
slug: r.slug,
|
|
98
|
+
name: r.name,
|
|
99
|
+
description: r.description,
|
|
100
|
+
activeMarkets: r.active_markets,
|
|
101
|
+
uniqueTraders: parseNumeric(r.unique_traders) ?? 0,
|
|
102
|
+
uniqueLps: parseNumeric(r.unique_lps) ?? 0,
|
|
103
|
+
totalVolume: parseNumeric(r.total_volume) ?? 0,
|
|
104
|
+
totalTrades: parseNumeric(r.total_trades) ?? 0,
|
|
105
|
+
positiveRoiPct: parseNumeric(r.positive_roi_pct) ?? 0,
|
|
106
|
+
medianRoiPct: parseNumeric(r.median_roi_pct),
|
|
107
|
+
}));
|
|
108
|
+
return { domains };
|
|
109
|
+
}
|
|
110
|
+
async getDomainDetail(slug) {
|
|
111
|
+
const summaries = await this.getDomains();
|
|
112
|
+
const summary = summaries.domains.find((d) => d.slug === slug);
|
|
113
|
+
if (!summary)
|
|
114
|
+
return null;
|
|
115
|
+
const ts = (await this.sql `
|
|
116
|
+
SELECT date, trade_count, volume_collateral, unique_traders, unique_lps, liquidity_net
|
|
117
|
+
FROM daily_domain_stats
|
|
118
|
+
WHERE domain_slug = ${slug}
|
|
119
|
+
ORDER BY date ASC
|
|
120
|
+
`);
|
|
121
|
+
const topTraders = (await this.sql `
|
|
122
|
+
SELECT
|
|
123
|
+
t.trader,
|
|
124
|
+
t.total_pnl,
|
|
125
|
+
t.total_trades,
|
|
126
|
+
t.total_volume,
|
|
127
|
+
t.roi_pct
|
|
128
|
+
FROM trader_lifetime t
|
|
129
|
+
WHERE t.domains_json @> ${JSON.stringify([slug])}::jsonb
|
|
130
|
+
ORDER BY t.total_pnl DESC NULLS LAST
|
|
131
|
+
LIMIT 25
|
|
132
|
+
`);
|
|
133
|
+
const topMarkets = (await this.sql `
|
|
134
|
+
SELECT
|
|
135
|
+
m.market_address,
|
|
136
|
+
SUM(m.volume_collateral) AS volume,
|
|
137
|
+
SUM(m.trade_count) AS trades,
|
|
138
|
+
MAX(m.unique_traders) AS unique_traders
|
|
139
|
+
FROM daily_market_stats m
|
|
140
|
+
JOIN market_domains md ON md.market_address = m.market_address
|
|
141
|
+
WHERE md.domain_slug = ${slug}
|
|
142
|
+
GROUP BY m.market_address
|
|
143
|
+
ORDER BY volume DESC
|
|
144
|
+
LIMIT 25
|
|
145
|
+
`);
|
|
146
|
+
return {
|
|
147
|
+
...summary,
|
|
148
|
+
timeSeries: ts.map((r) => ({
|
|
149
|
+
date: toDateString(r.date),
|
|
150
|
+
trades: parseNumeric(r.trade_count) ?? 0,
|
|
151
|
+
volume: parseNumeric(r.volume_collateral) ?? 0,
|
|
152
|
+
uniqueTraders: parseNumeric(r.unique_traders) ?? 0,
|
|
153
|
+
uniqueLps: parseNumeric(r.unique_lps) ?? 0,
|
|
154
|
+
liquidityNet: parseNumeric(r.liquidity_net) ?? 0,
|
|
155
|
+
})),
|
|
156
|
+
topTraders: topTraders.map((r) => ({
|
|
157
|
+
trader: r.trader,
|
|
158
|
+
totalPnl: parseNumeric(r.total_pnl) ?? 0,
|
|
159
|
+
totalTrades: r.total_trades,
|
|
160
|
+
volume: parseNumeric(r.total_volume) ?? 0,
|
|
161
|
+
roiPct: parseNumeric(r.roi_pct),
|
|
162
|
+
})),
|
|
163
|
+
topMarkets: topMarkets.map((r) => ({
|
|
164
|
+
marketAddress: r.market_address,
|
|
165
|
+
volume: parseNumeric(r.volume) ?? 0,
|
|
166
|
+
trades: parseNumeric(r.trades) ?? 0,
|
|
167
|
+
uniqueTraders: parseNumeric(r.unique_traders) ?? 0,
|
|
168
|
+
})),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
async getCohorts(filters) {
|
|
172
|
+
const rows = (await this.sql `
|
|
173
|
+
SELECT cohort_week, week_offset, cohort_size, retained
|
|
174
|
+
FROM cohort_retention
|
|
175
|
+
WHERE entity_type = ${filters.entityType}
|
|
176
|
+
AND domain_slug = ${filters.domain}
|
|
177
|
+
ORDER BY cohort_week ASC, week_offset ASC
|
|
178
|
+
`);
|
|
179
|
+
const cohorts = shapeCohortRows(rows);
|
|
180
|
+
const halfLives = [];
|
|
181
|
+
for (const c of cohorts) {
|
|
182
|
+
const hl = computeCohortHalfLife(c);
|
|
183
|
+
if (hl != null)
|
|
184
|
+
halfLives.push(hl);
|
|
185
|
+
}
|
|
186
|
+
halfLives.sort((a, b) => a - b);
|
|
187
|
+
const median = halfLives.length === 0
|
|
188
|
+
? null
|
|
189
|
+
: halfLives[Math.floor(halfLives.length / 2)] ?? null;
|
|
190
|
+
return {
|
|
191
|
+
entityType: filters.entityType,
|
|
192
|
+
domain: filters.domain,
|
|
193
|
+
cohorts,
|
|
194
|
+
medianHalfLifeWeeks: median,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async getRoi(filters) {
|
|
198
|
+
const [summary] = (await this.sql `
|
|
199
|
+
SELECT total_users, positive_roi_users, positive_roi_pct,
|
|
200
|
+
median_roi_pct, p25_roi_pct, p75_roi_pct, p90_roi_pct, mean_roi_pct
|
|
201
|
+
FROM roi_summary
|
|
202
|
+
WHERE entity_type = ${filters.entityType} AND domain_slug = ${filters.domain}
|
|
203
|
+
`);
|
|
204
|
+
const histogram = (await this.sql `
|
|
205
|
+
SELECT bucket_index, bucket_lower_pct, bucket_upper_pct, user_count
|
|
206
|
+
FROM roi_distribution
|
|
207
|
+
WHERE entity_type = ${filters.entityType} AND domain_slug = ${filters.domain}
|
|
208
|
+
ORDER BY bucket_index ASC
|
|
209
|
+
`);
|
|
210
|
+
return {
|
|
211
|
+
entityType: filters.entityType,
|
|
212
|
+
domain: filters.domain,
|
|
213
|
+
summary: {
|
|
214
|
+
totalUsers: summary?.total_users ?? 0,
|
|
215
|
+
positiveRoiUsers: summary?.positive_roi_users ?? 0,
|
|
216
|
+
positiveRoiPct: parseNumeric(summary?.positive_roi_pct) ?? 0,
|
|
217
|
+
medianRoiPct: parseNumeric(summary?.median_roi_pct ?? null),
|
|
218
|
+
p25RoiPct: parseNumeric(summary?.p25_roi_pct ?? null),
|
|
219
|
+
p75RoiPct: parseNumeric(summary?.p75_roi_pct ?? null),
|
|
220
|
+
p90RoiPct: parseNumeric(summary?.p90_roi_pct ?? null),
|
|
221
|
+
meanRoiPct: parseNumeric(summary?.mean_roi_pct ?? null),
|
|
222
|
+
},
|
|
223
|
+
histogram: histogram.map((b) => ({
|
|
224
|
+
bucketIndex: b.bucket_index,
|
|
225
|
+
bucketLowerPct: parseNumeric(b.bucket_lower_pct) ?? 0,
|
|
226
|
+
bucketUpperPct: parseNumeric(b.bucket_upper_pct) ?? 0,
|
|
227
|
+
userCount: b.user_count,
|
|
228
|
+
})),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
async getLeaderboard(filters) {
|
|
232
|
+
// Two paths: lifetime (no time-window) vs windowed (sums over daily facts).
|
|
233
|
+
const isWindowed = filters.from != null || filters.to != null;
|
|
234
|
+
const isDomainAll = filters.domain === 'all';
|
|
235
|
+
let entries;
|
|
236
|
+
if (!isWindowed && isDomainAll) {
|
|
237
|
+
// Fast path — lifetime totals.
|
|
238
|
+
const rows = (await this.sql `
|
|
239
|
+
SELECT trader, total_pnl, total_volume, total_trades,
|
|
240
|
+
markets_traded, domains_traded, roi_pct
|
|
241
|
+
FROM trader_lifetime
|
|
242
|
+
ORDER BY total_pnl DESC NULLS LAST
|
|
243
|
+
LIMIT ${filters.limit}
|
|
244
|
+
`);
|
|
245
|
+
entries = rows.map((r, i) => ({
|
|
246
|
+
rank: i + 1,
|
|
247
|
+
trader: r.trader,
|
|
248
|
+
totalPnl: parseNumeric(r.total_pnl) ?? 0,
|
|
249
|
+
totalVolume: parseNumeric(r.total_volume) ?? 0,
|
|
250
|
+
totalTrades: r.total_trades,
|
|
251
|
+
marketsTraded: r.markets_traded,
|
|
252
|
+
domainsTraded: r.domains_traded,
|
|
253
|
+
roiPct: parseNumeric(r.roi_pct),
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
else if (!isWindowed && !isDomainAll) {
|
|
257
|
+
// Domain filter, lifetime — use daily_trader_stats to filter, then join lifetime.
|
|
258
|
+
const rows = (await this.sql `
|
|
259
|
+
SELECT
|
|
260
|
+
t.trader,
|
|
261
|
+
t.total_pnl,
|
|
262
|
+
SUM(s.volume_collateral) AS total_volume,
|
|
263
|
+
SUM(s.trade_count) AS total_trades,
|
|
264
|
+
COUNT(DISTINCT s.date) AS active_days,
|
|
265
|
+
t.markets_traded,
|
|
266
|
+
t.domains_traded,
|
|
267
|
+
t.roi_pct
|
|
268
|
+
FROM daily_trader_stats s
|
|
269
|
+
JOIN trader_lifetime t ON t.trader = s.trader
|
|
270
|
+
WHERE s.domain_slug = ${filters.domain}
|
|
271
|
+
GROUP BY t.trader, t.total_pnl, t.markets_traded, t.domains_traded, t.roi_pct
|
|
272
|
+
ORDER BY t.total_pnl DESC NULLS LAST
|
|
273
|
+
LIMIT ${filters.limit}
|
|
274
|
+
`);
|
|
275
|
+
entries = rows.map((r, i) => ({
|
|
276
|
+
rank: i + 1,
|
|
277
|
+
trader: r.trader,
|
|
278
|
+
totalPnl: parseNumeric(r.total_pnl) ?? 0,
|
|
279
|
+
totalVolume: parseNumeric(r.total_volume) ?? 0,
|
|
280
|
+
totalTrades: parseNumeric(r.total_trades) ?? 0,
|
|
281
|
+
marketsTraded: r.markets_traded,
|
|
282
|
+
domainsTraded: r.domains_traded,
|
|
283
|
+
roiPct: parseNumeric(r.roi_pct),
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
// Windowed — sum daily facts within (from, to). Per-trader P&L isn't
|
|
288
|
+
// computable from daily facts (no per-day P&L bucket), so we sort by
|
|
289
|
+
// windowed VOLUME and report the trader's lifetime P&L alongside.
|
|
290
|
+
const rows = (await this.sql `
|
|
291
|
+
SELECT
|
|
292
|
+
s.trader,
|
|
293
|
+
SUM(s.volume_collateral) AS total_volume,
|
|
294
|
+
SUM(s.trade_count) AS total_trades,
|
|
295
|
+
COALESCE(t.total_pnl, 0) AS total_pnl,
|
|
296
|
+
t.roi_pct AS roi_pct,
|
|
297
|
+
COALESCE(t.markets_traded, 0) AS markets_traded,
|
|
298
|
+
COALESCE(t.domains_traded, 0) AS domains_traded
|
|
299
|
+
FROM daily_trader_stats s
|
|
300
|
+
LEFT JOIN trader_lifetime t ON t.trader = s.trader
|
|
301
|
+
WHERE (${filters.domain}::text = 'all' OR s.domain_slug = ${filters.domain})
|
|
302
|
+
AND (${filters.from}::date IS NULL OR s.date >= ${filters.from}::date)
|
|
303
|
+
AND (${filters.to}::date IS NULL OR s.date < ${filters.to}::date)
|
|
304
|
+
GROUP BY s.trader, t.total_pnl, t.roi_pct, t.markets_traded, t.domains_traded
|
|
305
|
+
ORDER BY total_volume DESC
|
|
306
|
+
LIMIT ${filters.limit}
|
|
307
|
+
`);
|
|
308
|
+
entries = rows.map((r, i) => ({
|
|
309
|
+
rank: i + 1,
|
|
310
|
+
trader: r.trader,
|
|
311
|
+
totalPnl: parseNumeric(r.total_pnl) ?? 0,
|
|
312
|
+
totalVolume: parseNumeric(r.total_volume) ?? 0,
|
|
313
|
+
totalTrades: parseNumeric(r.total_trades) ?? 0,
|
|
314
|
+
marketsTraded: r.markets_traded,
|
|
315
|
+
domainsTraded: r.domains_traded,
|
|
316
|
+
roiPct: parseNumeric(r.roi_pct),
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
entries,
|
|
321
|
+
domain: filters.domain,
|
|
322
|
+
windowFrom: filters.from,
|
|
323
|
+
windowTo: filters.to,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
async getTraderAnalytics(trader) {
|
|
327
|
+
const traderLower = trader.toLowerCase();
|
|
328
|
+
const [lifetime] = (await this.sql `
|
|
329
|
+
SELECT trader, first_trade_at, last_trade_at, cohort_week,
|
|
330
|
+
total_trades, buy_count, sell_count, markets_traded,
|
|
331
|
+
domains_traded, domains_json, total_volume, total_collateral_at_risk,
|
|
332
|
+
realized_pnl, unrealized_pnl, total_pnl, roi_pct, active_days
|
|
333
|
+
FROM trader_lifetime
|
|
334
|
+
WHERE LOWER(trader) = ${traderLower}
|
|
335
|
+
`);
|
|
336
|
+
const perDomain = (await this.sql `
|
|
337
|
+
SELECT
|
|
338
|
+
s.domain_slug AS slug,
|
|
339
|
+
d.name,
|
|
340
|
+
SUM(s.trade_count) AS trade_count,
|
|
341
|
+
SUM(s.volume_collateral) AS volume,
|
|
342
|
+
MAX(s.markets_traded) AS markets_traded,
|
|
343
|
+
COUNT(DISTINCT s.date) AS days_active
|
|
344
|
+
FROM daily_trader_stats s
|
|
345
|
+
JOIN domains d ON d.slug = s.domain_slug
|
|
346
|
+
WHERE LOWER(s.trader) = ${traderLower}
|
|
347
|
+
GROUP BY s.domain_slug, d.name, d.sort_order
|
|
348
|
+
ORDER BY d.sort_order ASC
|
|
349
|
+
`);
|
|
350
|
+
let cohort = null;
|
|
351
|
+
if (lifetime?.cohort_week) {
|
|
352
|
+
const cohortWeek = toDateString(lifetime.cohort_week);
|
|
353
|
+
const cohortRows = (await this.sql `
|
|
354
|
+
SELECT cohort_week, week_offset, cohort_size, retained
|
|
355
|
+
FROM cohort_retention
|
|
356
|
+
WHERE entity_type = 'trader' AND domain_slug = 'all'
|
|
357
|
+
AND cohort_week = ${cohortWeek}::date
|
|
358
|
+
ORDER BY week_offset ASC
|
|
359
|
+
`);
|
|
360
|
+
const shaped = shapeCohortRows(cohortRows);
|
|
361
|
+
const halfLife = shaped[0] ? computeCohortHalfLife(shaped[0]) : null;
|
|
362
|
+
const latest = cohortRows[cohortRows.length - 1];
|
|
363
|
+
cohort = {
|
|
364
|
+
cohortWeek,
|
|
365
|
+
halfLifeWeeks: halfLife,
|
|
366
|
+
cohortSize: cohortRows[0]?.cohort_size ?? null,
|
|
367
|
+
retainedThisWeek: latest?.retained ?? null,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
trader,
|
|
372
|
+
lifetime: lifetime
|
|
373
|
+
? {
|
|
374
|
+
firstTradeAt: dateToUnixSeconds(lifetime.first_trade_at),
|
|
375
|
+
lastTradeAt: dateToUnixSeconds(lifetime.last_trade_at),
|
|
376
|
+
cohortWeek: lifetime.cohort_week ? toDateString(lifetime.cohort_week) : null,
|
|
377
|
+
totalTrades: lifetime.total_trades,
|
|
378
|
+
buyCount: lifetime.buy_count,
|
|
379
|
+
sellCount: lifetime.sell_count,
|
|
380
|
+
marketsTraded: lifetime.markets_traded,
|
|
381
|
+
domainsTraded: lifetime.domains_traded,
|
|
382
|
+
domains: parseDomainsJson(lifetime.domains_json),
|
|
383
|
+
totalVolume: parseNumeric(lifetime.total_volume) ?? 0,
|
|
384
|
+
totalCollateralAtRisk: parseNumeric(lifetime.total_collateral_at_risk) ?? 0,
|
|
385
|
+
realizedPnl: parseNumeric(lifetime.realized_pnl) ?? 0,
|
|
386
|
+
unrealizedPnl: parseNumeric(lifetime.unrealized_pnl) ?? 0,
|
|
387
|
+
totalPnl: parseNumeric(lifetime.total_pnl) ?? 0,
|
|
388
|
+
roiPct: parseNumeric(lifetime.roi_pct),
|
|
389
|
+
activeDays: lifetime.active_days,
|
|
390
|
+
}
|
|
391
|
+
: null,
|
|
392
|
+
perDomain: perDomain.map((r) => ({
|
|
393
|
+
slug: r.slug,
|
|
394
|
+
name: r.name,
|
|
395
|
+
tradeCount: parseNumeric(r.trade_count) ?? 0,
|
|
396
|
+
volume: parseNumeric(r.volume) ?? 0,
|
|
397
|
+
marketsTraded: parseNumeric(r.markets_traded) ?? 0,
|
|
398
|
+
daysActive: parseNumeric(r.days_active) ?? 0,
|
|
399
|
+
})),
|
|
400
|
+
cohort,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function parseDomainsJson(raw) {
|
|
405
|
+
if (Array.isArray(raw))
|
|
406
|
+
return raw;
|
|
407
|
+
if (typeof raw === 'string') {
|
|
408
|
+
try {
|
|
409
|
+
const parsed = JSON.parse(raw);
|
|
410
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return [];
|
|
417
|
+
}
|
|
418
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../../src/warehouse/repositories/analytics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Warehouse DDL — domain taxonomy + analytics fact / lifetime / cohort / ROI tables.
|
|
3
|
+
*
|
|
4
|
+
* Idempotent: safe to run on every boot. Postgres handles `IF NOT EXISTS` cleanly,
|
|
5
|
+
* so this acts as both initial creation and a lightweight migration runner.
|
|
6
|
+
*/
|
|
7
|
+
import type { SQL } from 'bun';
|
|
8
|
+
export declare function initializeWarehouseSchema(sql: SQL): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/warehouse/schema.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AA8O/B,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAIvE"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
const SCHEMA_DDL = [
|
|
2
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
3
|
+
// Domains — canonical taxonomy
|
|
4
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
5
|
+
`CREATE TABLE IF NOT EXISTS domains (
|
|
6
|
+
slug TEXT PRIMARY KEY,
|
|
7
|
+
name TEXT NOT NULL,
|
|
8
|
+
description TEXT NOT NULL DEFAULT '',
|
|
9
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
10
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
11
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
12
|
+
)`,
|
|
13
|
+
`CREATE TABLE IF NOT EXISTS market_domains (
|
|
14
|
+
market_address TEXT NOT NULL,
|
|
15
|
+
domain_slug TEXT NOT NULL REFERENCES domains(slug) ON DELETE CASCADE,
|
|
16
|
+
is_primary BOOLEAN NOT NULL DEFAULT FALSE,
|
|
17
|
+
source TEXT NOT NULL DEFAULT 'auto',
|
|
18
|
+
assigned_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
19
|
+
PRIMARY KEY (market_address, domain_slug)
|
|
20
|
+
)`,
|
|
21
|
+
`CREATE INDEX IF NOT EXISTS idx_market_domains_domain
|
|
22
|
+
ON market_domains(domain_slug)`,
|
|
23
|
+
`CREATE INDEX IF NOT EXISTS idx_market_domains_market
|
|
24
|
+
ON market_domains(market_address)`,
|
|
25
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_market_domains_one_primary
|
|
26
|
+
ON market_domains(market_address) WHERE is_primary`,
|
|
27
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
28
|
+
// Daily fact tables — date × dimension × metrics
|
|
29
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
30
|
+
`CREATE TABLE IF NOT EXISTS daily_trader_stats (
|
|
31
|
+
date DATE NOT NULL,
|
|
32
|
+
trader TEXT NOT NULL,
|
|
33
|
+
domain_slug TEXT NOT NULL REFERENCES domains(slug),
|
|
34
|
+
trade_count INTEGER NOT NULL DEFAULT 0,
|
|
35
|
+
buy_count INTEGER NOT NULL DEFAULT 0,
|
|
36
|
+
sell_count INTEGER NOT NULL DEFAULT 0,
|
|
37
|
+
volume_collateral NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
38
|
+
realized_pnl NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
39
|
+
unrealized_pnl NUMERIC(38, 6),
|
|
40
|
+
markets_traded INTEGER NOT NULL DEFAULT 0,
|
|
41
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
42
|
+
PRIMARY KEY (date, trader, domain_slug)
|
|
43
|
+
)`,
|
|
44
|
+
`CREATE INDEX IF NOT EXISTS idx_daily_trader_stats_trader
|
|
45
|
+
ON daily_trader_stats(trader, date DESC)`,
|
|
46
|
+
`CREATE INDEX IF NOT EXISTS idx_daily_trader_stats_domain_date
|
|
47
|
+
ON daily_trader_stats(domain_slug, date DESC)`,
|
|
48
|
+
`CREATE TABLE IF NOT EXISTS daily_lp_stats (
|
|
49
|
+
date DATE NOT NULL,
|
|
50
|
+
provider TEXT NOT NULL,
|
|
51
|
+
domain_slug TEXT NOT NULL REFERENCES domains(slug),
|
|
52
|
+
deposit_count INTEGER NOT NULL DEFAULT 0,
|
|
53
|
+
withdraw_count INTEGER NOT NULL DEFAULT 0,
|
|
54
|
+
deposited NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
55
|
+
withdrawn NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
56
|
+
net_position NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
57
|
+
unrealized_pnl NUMERIC(38, 6),
|
|
58
|
+
markets_active INTEGER NOT NULL DEFAULT 0,
|
|
59
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
60
|
+
PRIMARY KEY (date, provider, domain_slug)
|
|
61
|
+
)`,
|
|
62
|
+
`CREATE INDEX IF NOT EXISTS idx_daily_lp_stats_provider
|
|
63
|
+
ON daily_lp_stats(provider, date DESC)`,
|
|
64
|
+
`CREATE INDEX IF NOT EXISTS idx_daily_lp_stats_domain_date
|
|
65
|
+
ON daily_lp_stats(domain_slug, date DESC)`,
|
|
66
|
+
`CREATE TABLE IF NOT EXISTS daily_market_stats (
|
|
67
|
+
date DATE NOT NULL,
|
|
68
|
+
market_address TEXT NOT NULL,
|
|
69
|
+
trade_count INTEGER NOT NULL DEFAULT 0,
|
|
70
|
+
unique_traders INTEGER NOT NULL DEFAULT 0,
|
|
71
|
+
volume_collateral NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
72
|
+
liquidity_added NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
73
|
+
liquidity_removed NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
74
|
+
unique_lps INTEGER NOT NULL DEFAULT 0,
|
|
75
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
76
|
+
PRIMARY KEY (date, market_address)
|
|
77
|
+
)`,
|
|
78
|
+
`CREATE INDEX IF NOT EXISTS idx_daily_market_stats_market
|
|
79
|
+
ON daily_market_stats(market_address, date DESC)`,
|
|
80
|
+
`CREATE TABLE IF NOT EXISTS daily_domain_stats (
|
|
81
|
+
date DATE NOT NULL,
|
|
82
|
+
domain_slug TEXT NOT NULL REFERENCES domains(slug),
|
|
83
|
+
trade_count INTEGER NOT NULL DEFAULT 0,
|
|
84
|
+
unique_traders INTEGER NOT NULL DEFAULT 0,
|
|
85
|
+
unique_lps INTEGER NOT NULL DEFAULT 0,
|
|
86
|
+
volume_collateral NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
87
|
+
liquidity_net NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
88
|
+
active_markets INTEGER NOT NULL DEFAULT 0,
|
|
89
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
90
|
+
PRIMARY KEY (date, domain_slug)
|
|
91
|
+
)`,
|
|
92
|
+
`CREATE INDEX IF NOT EXISTS idx_daily_domain_stats_date
|
|
93
|
+
ON daily_domain_stats(date DESC)`,
|
|
94
|
+
`CREATE TABLE IF NOT EXISTS daily_platform_stats (
|
|
95
|
+
date DATE PRIMARY KEY,
|
|
96
|
+
trade_count INTEGER NOT NULL DEFAULT 0,
|
|
97
|
+
unique_traders INTEGER NOT NULL DEFAULT 0,
|
|
98
|
+
unique_lps INTEGER NOT NULL DEFAULT 0,
|
|
99
|
+
new_traders INTEGER NOT NULL DEFAULT 0,
|
|
100
|
+
new_lps INTEGER NOT NULL DEFAULT 0,
|
|
101
|
+
volume_collateral NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
102
|
+
liquidity_net NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
103
|
+
active_markets INTEGER NOT NULL DEFAULT 0,
|
|
104
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
105
|
+
)`,
|
|
106
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
107
|
+
// Lifetime / CLTV — one row per entity
|
|
108
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
109
|
+
`CREATE TABLE IF NOT EXISTS trader_lifetime (
|
|
110
|
+
trader TEXT PRIMARY KEY,
|
|
111
|
+
first_trade_at TIMESTAMPTZ,
|
|
112
|
+
last_trade_at TIMESTAMPTZ,
|
|
113
|
+
cohort_week DATE,
|
|
114
|
+
total_trades INTEGER NOT NULL DEFAULT 0,
|
|
115
|
+
buy_count INTEGER NOT NULL DEFAULT 0,
|
|
116
|
+
sell_count INTEGER NOT NULL DEFAULT 0,
|
|
117
|
+
markets_traded INTEGER NOT NULL DEFAULT 0,
|
|
118
|
+
domains_traded INTEGER NOT NULL DEFAULT 0,
|
|
119
|
+
domains_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
120
|
+
total_volume NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
121
|
+
total_collateral_at_risk NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
122
|
+
realized_pnl NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
123
|
+
unrealized_pnl NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
124
|
+
total_pnl NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
125
|
+
roi_pct NUMERIC(10, 4),
|
|
126
|
+
active_days INTEGER NOT NULL DEFAULT 0,
|
|
127
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
128
|
+
)`,
|
|
129
|
+
`CREATE INDEX IF NOT EXISTS idx_trader_lifetime_pnl
|
|
130
|
+
ON trader_lifetime(total_pnl DESC)`,
|
|
131
|
+
`CREATE INDEX IF NOT EXISTS idx_trader_lifetime_roi
|
|
132
|
+
ON trader_lifetime(roi_pct DESC NULLS LAST)`,
|
|
133
|
+
`CREATE INDEX IF NOT EXISTS idx_trader_lifetime_cohort
|
|
134
|
+
ON trader_lifetime(cohort_week)`,
|
|
135
|
+
`CREATE INDEX IF NOT EXISTS idx_trader_lifetime_volume
|
|
136
|
+
ON trader_lifetime(total_volume DESC)`,
|
|
137
|
+
`CREATE TABLE IF NOT EXISTS lp_lifetime (
|
|
138
|
+
provider TEXT PRIMARY KEY,
|
|
139
|
+
first_deposit_at TIMESTAMPTZ,
|
|
140
|
+
last_activity_at TIMESTAMPTZ,
|
|
141
|
+
cohort_week DATE,
|
|
142
|
+
deposit_count INTEGER NOT NULL DEFAULT 0,
|
|
143
|
+
withdraw_count INTEGER NOT NULL DEFAULT 0,
|
|
144
|
+
total_deposited NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
145
|
+
total_withdrawn NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
146
|
+
net_position NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
147
|
+
current_value NUMERIC(38, 6),
|
|
148
|
+
unrealized_pnl NUMERIC(38, 6),
|
|
149
|
+
realized_pnl NUMERIC(38, 6) NOT NULL DEFAULT 0,
|
|
150
|
+
markets_provided INTEGER NOT NULL DEFAULT 0,
|
|
151
|
+
domains_provided INTEGER NOT NULL DEFAULT 0,
|
|
152
|
+
domains_json JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
153
|
+
active_days INTEGER NOT NULL DEFAULT 0,
|
|
154
|
+
roi_pct NUMERIC(10, 4),
|
|
155
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
156
|
+
)`,
|
|
157
|
+
`CREATE INDEX IF NOT EXISTS idx_lp_lifetime_pnl
|
|
158
|
+
ON lp_lifetime(unrealized_pnl DESC)`,
|
|
159
|
+
`CREATE INDEX IF NOT EXISTS idx_lp_lifetime_cohort
|
|
160
|
+
ON lp_lifetime(cohort_week)`,
|
|
161
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
162
|
+
// Cohort retention — week × week_offset × domain
|
|
163
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
164
|
+
`CREATE TABLE IF NOT EXISTS cohort_retention (
|
|
165
|
+
cohort_week DATE NOT NULL,
|
|
166
|
+
week_offset INTEGER NOT NULL,
|
|
167
|
+
domain_slug TEXT NOT NULL,
|
|
168
|
+
entity_type TEXT NOT NULL,
|
|
169
|
+
cohort_size INTEGER NOT NULL,
|
|
170
|
+
retained INTEGER NOT NULL,
|
|
171
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
172
|
+
PRIMARY KEY (cohort_week, week_offset, domain_slug, entity_type)
|
|
173
|
+
)`,
|
|
174
|
+
`CREATE INDEX IF NOT EXISTS idx_cohort_retention_lookup
|
|
175
|
+
ON cohort_retention(domain_slug, entity_type, cohort_week)`,
|
|
176
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
177
|
+
// ROI distribution — bucketed histograms + summary stats
|
|
178
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
179
|
+
`CREATE TABLE IF NOT EXISTS roi_distribution (
|
|
180
|
+
domain_slug TEXT NOT NULL,
|
|
181
|
+
entity_type TEXT NOT NULL,
|
|
182
|
+
bucket_index INTEGER NOT NULL,
|
|
183
|
+
bucket_lower_pct NUMERIC(10, 4) NOT NULL,
|
|
184
|
+
bucket_upper_pct NUMERIC(10, 4) NOT NULL,
|
|
185
|
+
user_count INTEGER NOT NULL DEFAULT 0,
|
|
186
|
+
computed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
187
|
+
PRIMARY KEY (domain_slug, entity_type, bucket_index)
|
|
188
|
+
)`,
|
|
189
|
+
`CREATE TABLE IF NOT EXISTS roi_summary (
|
|
190
|
+
domain_slug TEXT NOT NULL,
|
|
191
|
+
entity_type TEXT NOT NULL,
|
|
192
|
+
total_users INTEGER NOT NULL DEFAULT 0,
|
|
193
|
+
positive_roi_users INTEGER NOT NULL DEFAULT 0,
|
|
194
|
+
positive_roi_pct NUMERIC(10, 4) NOT NULL DEFAULT 0,
|
|
195
|
+
median_roi_pct NUMERIC(10, 4),
|
|
196
|
+
p25_roi_pct NUMERIC(10, 4),
|
|
197
|
+
p75_roi_pct NUMERIC(10, 4),
|
|
198
|
+
p90_roi_pct NUMERIC(10, 4),
|
|
199
|
+
mean_roi_pct NUMERIC(10, 4),
|
|
200
|
+
computed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
201
|
+
PRIMARY KEY (domain_slug, entity_type)
|
|
202
|
+
)`,
|
|
203
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
204
|
+
// Aggregator bookkeeping
|
|
205
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
206
|
+
`CREATE TABLE IF NOT EXISTS aggregator_state (
|
|
207
|
+
job_name TEXT PRIMARY KEY,
|
|
208
|
+
last_event_id BIGINT NOT NULL DEFAULT 0,
|
|
209
|
+
last_run_at TIMESTAMPTZ,
|
|
210
|
+
last_success_at TIMESTAMPTZ,
|
|
211
|
+
last_error TEXT
|
|
212
|
+
)`,
|
|
213
|
+
];
|
|
214
|
+
export async function initializeWarehouseSchema(sql) {
|
|
215
|
+
for (const statement of SCHEMA_DDL) {
|
|
216
|
+
await sql.unsafe(statement);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/warehouse/schema.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,GAAsB;IACpC,yEAAyE;IACzE,+BAA+B;IAC/B,yEAAyE;IACzE;;;;;;;IAOE;IAEF;;;;;;;IAOE;IAEF;mCACiC;IAEjC;sCACoC;IAEpC;uDACqD;IAErD,yEAAyE;IACzE,iDAAiD;IACjD,yEAAyE;IACzE;;;;;;;;;;;;;IAaE;IAEF;6CAC2C;IAC3C;kDACgD;IAEhD;;;;;;;;;;;;;IAaE;IAEF;2CACyC;IACzC;8CAC4C;IAE5C;;;;;;;;;;;IAWE;IAEF;qDACmD;IAEnD;;;;;;;;;;;IAWE;IAEF;qCACmC;IAEnC;;;;;;;;;;;IAWE;IAEF,yEAAyE;IACzE,uCAAuC;IACvC,yEAAyE;IACzE;;;;;;;;;;;;;;;;;;;IAmBE;IAEF;uCACqC;IACrC;gDAC8C;IAC9C;oCACkC;IAClC;0CACwC;IAExC;;;;;;;;;;;;;;;;;;;IAmBE;IAEF;wCACsC;IACtC;gCAC8B;IAE9B,yEAAyE;IACzE,iDAAiD;IACjD,yEAAyE;IACzE;;;;;;;;;IASE;IAEF;+DAC6D;IAE7D,yEAAyE;IACzE,yDAAyD;IACzD,yEAAyE;IACzE;;;;;;;;;IASE;IAEF;;;;;;;;;;;;;IAaE;IAEF,yEAAyE;IACzE,yBAAyB;IACzB,yEAAyE;IACzE;;;;;;IAME;CACH,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,GAAQ;IACtD,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC"}
|