@the-situation/indexer 0.17.0 → 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.
Files changed (133) hide show
  1. package/README.md +43 -4
  2. package/dist/aggregator/cohort.d.ts +30 -0
  3. package/dist/aggregator/cohort.d.ts.map +1 -0
  4. package/dist/aggregator/cohort.js +153 -0
  5. package/dist/aggregator/cohort.js.map +1 -0
  6. package/dist/aggregator/daily.d.ts +71 -0
  7. package/dist/aggregator/daily.d.ts.map +1 -0
  8. package/dist/aggregator/daily.js +249 -0
  9. package/dist/aggregator/daily.js.map +1 -0
  10. package/dist/aggregator/domains.d.ts +20 -0
  11. package/dist/aggregator/domains.d.ts.map +1 -0
  12. package/dist/aggregator/domains.js +38 -0
  13. package/dist/aggregator/domains.js.map +1 -0
  14. package/dist/aggregator/index.d.ts +31 -0
  15. package/dist/aggregator/index.d.ts.map +1 -0
  16. package/dist/aggregator/index.js +100 -0
  17. package/dist/aggregator/index.js.map +1 -0
  18. package/dist/aggregator/lifetime.d.ts +52 -0
  19. package/dist/aggregator/lifetime.d.ts.map +1 -0
  20. package/dist/aggregator/lifetime.js +222 -0
  21. package/dist/aggregator/lifetime.js.map +1 -0
  22. package/dist/aggregator/roi.d.ts +42 -0
  23. package/dist/aggregator/roi.d.ts.map +1 -0
  24. package/dist/aggregator/roi.js +153 -0
  25. package/dist/aggregator/roi.js.map +1 -0
  26. package/dist/aggregator/sources.d.ts +59 -0
  27. package/dist/aggregator/sources.d.ts.map +1 -0
  28. package/dist/aggregator/sources.js +53 -0
  29. package/dist/aggregator/sources.js.map +1 -0
  30. package/dist/aggregator/writers.d.ts +22 -0
  31. package/dist/aggregator/writers.d.ts.map +1 -0
  32. package/dist/aggregator/writers.js +147 -0
  33. package/dist/aggregator/writers.js.map +1 -0
  34. package/dist/api/app.d.ts +177 -13
  35. package/dist/api/app.d.ts.map +1 -1
  36. package/dist/api/app.js +4 -3
  37. package/dist/api/app.js.map +1 -1
  38. package/dist/api/routes/admin-subscriptions.d.ts +11 -11
  39. package/dist/api/routes/analytics.d.ts +205 -0
  40. package/dist/api/routes/analytics.d.ts.map +1 -0
  41. package/dist/api/routes/analytics.js +122 -0
  42. package/dist/api/routes/analytics.js.map +1 -0
  43. package/dist/api/routes/health.d.ts.map +1 -1
  44. package/dist/api/routes/health.js +2 -0
  45. package/dist/api/routes/health.js.map +1 -1
  46. package/dist/api/routes/index.d.ts +1 -0
  47. package/dist/api/routes/index.d.ts.map +1 -1
  48. package/dist/api/routes/index.js +1 -0
  49. package/dist/api/routes/index.js.map +1 -1
  50. package/dist/api/routes/rankings.d.ts +17 -3
  51. package/dist/api/routes/rankings.d.ts.map +1 -1
  52. package/dist/api/routes/rankings.js +44 -5
  53. package/dist/api/routes/rankings.js.map +1 -1
  54. package/dist/api/routes/trader-stats.d.ts +11 -2
  55. package/dist/api/routes/trader-stats.d.ts.map +1 -1
  56. package/dist/api/routes/trader-stats.js +72 -2
  57. package/dist/api/routes/trader-stats.js.map +1 -1
  58. package/dist/client/IndexerClient.d.ts +30 -1
  59. package/dist/client/IndexerClient.d.ts.map +1 -1
  60. package/dist/client/IndexerClient.js.map +1 -1
  61. package/dist/client/IndexerClientLive.d.ts.map +1 -1
  62. package/dist/client/IndexerClientLive.js +50 -1
  63. package/dist/client/IndexerClientLive.js.map +1 -1
  64. package/dist/client/convenience.d.ts +9 -2
  65. package/dist/client/convenience.d.ts.map +1 -1
  66. package/dist/client/convenience.js +9 -1
  67. package/dist/client/convenience.js.map +1 -1
  68. package/dist/client/index.d.ts +3 -2
  69. package/dist/client/index.d.ts.map +1 -1
  70. package/dist/client/index.js +1 -1
  71. package/dist/client/index.js.map +1 -1
  72. package/dist/config.d.ts +17 -1
  73. package/dist/config.d.ts.map +1 -1
  74. package/dist/config.js +39 -3
  75. package/dist/config.js.map +1 -1
  76. package/dist/etl/event-indexer.d.ts +1 -1
  77. package/dist/etl/lp-position-refresher.d.ts +2 -1
  78. package/dist/etl/lp-position-refresher.d.ts.map +1 -1
  79. package/dist/etl/lp-position-refresher.js +24 -11
  80. package/dist/etl/lp-position-refresher.js.map +1 -1
  81. package/dist/index.js +64 -8
  82. package/dist/index.js.map +1 -1
  83. package/dist/layers/ChainReaderLive.d.ts.map +1 -1
  84. package/dist/layers/ChainReaderLive.js +15 -0
  85. package/dist/layers/ChainReaderLive.js.map +1 -1
  86. package/dist/layers/VoyagerClientLive.d.ts +2 -1
  87. package/dist/layers/VoyagerClientLive.d.ts.map +1 -1
  88. package/dist/layers/VoyagerClientLive.js +89 -55
  89. package/dist/layers/VoyagerClientLive.js.map +1 -1
  90. package/dist/services/ChainReader.d.ts +10 -0
  91. package/dist/services/ChainReader.d.ts.map +1 -1
  92. package/dist/services/ChainReader.js.map +1 -1
  93. package/dist/services/VoyagerRateLimit.d.ts +11 -8
  94. package/dist/services/VoyagerRateLimit.d.ts.map +1 -1
  95. package/dist/services/VoyagerRateLimit.js +51 -26
  96. package/dist/services/VoyagerRateLimit.js.map +1 -1
  97. package/dist/types/analytics.d.ts +194 -0
  98. package/dist/types/analytics.d.ts.map +1 -0
  99. package/dist/types/analytics.js +10 -0
  100. package/dist/types/analytics.js.map +1 -0
  101. package/dist/types/api.d.ts +2 -0
  102. package/dist/types/api.d.ts.map +1 -1
  103. package/dist/types/index.d.ts +1 -0
  104. package/dist/types/index.d.ts.map +1 -1
  105. package/dist/warehouse/analytics-helpers.d.ts +36 -0
  106. package/dist/warehouse/analytics-helpers.d.ts.map +1 -0
  107. package/dist/warehouse/analytics-helpers.js +142 -0
  108. package/dist/warehouse/analytics-helpers.js.map +1 -0
  109. package/dist/warehouse/backfill.d.ts +26 -0
  110. package/dist/warehouse/backfill.d.ts.map +1 -0
  111. package/dist/warehouse/backfill.js +77 -0
  112. package/dist/warehouse/backfill.js.map +1 -0
  113. package/dist/warehouse/connection.d.ts +18 -0
  114. package/dist/warehouse/connection.d.ts.map +1 -0
  115. package/dist/warehouse/connection.js +24 -0
  116. package/dist/warehouse/connection.js.map +1 -0
  117. package/dist/warehouse/index.d.ts +14 -0
  118. package/dist/warehouse/index.d.ts.map +1 -0
  119. package/dist/warehouse/index.js +14 -0
  120. package/dist/warehouse/index.js.map +1 -0
  121. package/dist/warehouse/repositories/analytics.d.ts +61 -0
  122. package/dist/warehouse/repositories/analytics.d.ts.map +1 -0
  123. package/dist/warehouse/repositories/analytics.js +418 -0
  124. package/dist/warehouse/repositories/analytics.js.map +1 -0
  125. package/dist/warehouse/schema.d.ts +9 -0
  126. package/dist/warehouse/schema.d.ts.map +1 -0
  127. package/dist/warehouse/schema.js +219 -0
  128. package/dist/warehouse/schema.js.map +1 -0
  129. package/dist/warehouse/seed-domains.d.ts +29 -0
  130. package/dist/warehouse/seed-domains.d.ts.map +1 -0
  131. package/dist/warehouse/seed-domains.js +223 -0
  132. package/dist/warehouse/seed-domains.js.map +1 -0
  133. package/package.json +1 -1
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Postgres warehouse connection (Bun.sql).
3
+ *
4
+ * The warehouse holds derived analytics tables (domains, daily fact tables,
5
+ * cohort retention, ROI distributions) that are populated by the aggregator
6
+ * job from the SQLite OLTP store. SQLite remains the source of truth for raw
7
+ * events; Postgres is the OLAP layer.
8
+ *
9
+ * Bun.sql is the project standard (see CLAUDE.md). Do not introduce `pg` or
10
+ * `postgres.js`.
11
+ */
12
+ import { SQL } from 'bun';
13
+ export async function createWarehouse(url) {
14
+ const sql = new SQL(url);
15
+ // Ping to fail fast on bad credentials / unreachable host.
16
+ await sql `SELECT 1`;
17
+ return {
18
+ sql,
19
+ close: async () => {
20
+ await sql.end();
21
+ },
22
+ };
23
+ }
24
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/warehouse/connection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAO1B,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW;IAC/C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACzB,2DAA2D;IAC3D,MAAM,GAAG,CAAA,UAAU,CAAC;IACpB,OAAO;QACL,GAAG;QACH,KAAK,EAAE,KAAK,IAAI,EAAE;YAChB,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Warehouse barrel export.
3
+ *
4
+ * The warehouse is the Postgres OLAP layer that sits next to the SQLite OLTP
5
+ * store. It holds derived analytics tables (domains, daily fact tables,
6
+ * lifetime/CLTV, cohort retention, ROI distributions). All writes flow through
7
+ * the aggregator job; reads are exposed via `/api/analytics/*` and via the
8
+ * domain/time-window-filtered variants of `/api/rankings` and `/api/positions/:trader/stats`.
9
+ */
10
+ export { createWarehouse, type Warehouse } from './connection';
11
+ export { initializeWarehouseSchema } from './schema';
12
+ export { CANONICAL_DOMAINS, classifyTopic, seedCanonicalDomains, type CanonicalDomain, } from './seed-domains';
13
+ export { backfillMarketDomains, type BackfillResult } from './backfill';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/warehouse/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,oBAAoB,EACpB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Warehouse barrel export.
3
+ *
4
+ * The warehouse is the Postgres OLAP layer that sits next to the SQLite OLTP
5
+ * store. It holds derived analytics tables (domains, daily fact tables,
6
+ * lifetime/CLTV, cohort retention, ROI distributions). All writes flow through
7
+ * the aggregator job; reads are exposed via `/api/analytics/*` and via the
8
+ * domain/time-window-filtered variants of `/api/rankings` and `/api/positions/:trader/stats`.
9
+ */
10
+ export { createWarehouse } from './connection';
11
+ export { initializeWarehouseSchema } from './schema';
12
+ export { CANONICAL_DOMAINS, classifyTopic, seedCanonicalDomains, } from './seed-domains';
13
+ export { backfillMarketDomains } from './backfill';
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/warehouse/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,eAAe,EAAkB,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AACrD,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,oBAAoB,GAErB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,qBAAqB,EAAuB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Analytics warehouse repository.
3
+ *
4
+ * All `/api/analytics/*` routes go through this single class. Each method
5
+ * takes a parsed/validated AnalyticsFilters and returns a public response
6
+ * shape. Methods are async (Bun.sql is async).
7
+ *
8
+ * Performance:
9
+ * - Indexed lookups everywhere: see warehouse/schema.ts for column-coverage.
10
+ * - The "top traders by domain" and "leaderboard with time-window" queries
11
+ * can be expensive on the daily fact tables. We rely on
12
+ * idx_daily_trader_stats_domain_date (covering filter + sort prefix) and
13
+ * idx_trader_lifetime_pnl. With ~10k–100k traders this is sub-100ms.
14
+ * - All NUMERIC columns return as strings; we coerce to JS number at the
15
+ * boundary via `parseNumeric` to keep the public API plain numbers.
16
+ *
17
+ * Memory:
18
+ * - We never SELECT the entire fact table — every public method has a LIMIT
19
+ * clause or aggregates by date.
20
+ * - Per-domain rollups for the overview endpoint sum directly in SQL; we
21
+ * don't ship per-trader rows back to JS.
22
+ */
23
+ import type { SQL } from 'bun';
24
+ import type { AnalyticsFilters, AnalyticsLeaderboardResponse, AnalyticsOverviewResponse, CohortsResponse, DomainDetailResponse, DomainsResponse, EntityType, RoiResponse, TraderAnalyticsResponse } from '../../types/analytics';
25
+ export interface AnalyticsRepository {
26
+ getOverview(): Promise<AnalyticsOverviewResponse>;
27
+ getDomains(): Promise<DomainsResponse>;
28
+ getDomainDetail(slug: string): Promise<DomainDetailResponse | null>;
29
+ getCohorts(filters: {
30
+ entityType: EntityType;
31
+ domain: string;
32
+ }): Promise<CohortsResponse>;
33
+ getRoi(filters: {
34
+ entityType: EntityType;
35
+ domain: string;
36
+ }): Promise<RoiResponse>;
37
+ getLeaderboard(filters: AnalyticsFilters & {
38
+ limit: number;
39
+ }): Promise<AnalyticsLeaderboardResponse>;
40
+ getTraderAnalytics(trader: string): Promise<TraderAnalyticsResponse>;
41
+ }
42
+ export declare class PgAnalyticsRepository implements AnalyticsRepository {
43
+ private readonly sql;
44
+ constructor(sql: SQL);
45
+ getOverview(): Promise<AnalyticsOverviewResponse>;
46
+ getDomains(): Promise<DomainsResponse>;
47
+ getDomainDetail(slug: string): Promise<DomainDetailResponse | null>;
48
+ getCohorts(filters: {
49
+ entityType: EntityType;
50
+ domain: string;
51
+ }): Promise<CohortsResponse>;
52
+ getRoi(filters: {
53
+ entityType: EntityType;
54
+ domain: string;
55
+ }): Promise<RoiResponse>;
56
+ getLeaderboard(filters: AnalyticsFilters & {
57
+ limit: number;
58
+ }): Promise<AnalyticsLeaderboardResponse>;
59
+ getTraderAnalytics(trader: string): Promise<TraderAnalyticsResponse>;
60
+ }
61
+ //# sourceMappingURL=analytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../src/warehouse/repositories/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC/B,OAAO,KAAK,EACV,gBAAgB,EAEhB,4BAA4B,EAC5B,yBAAyB,EACzB,eAAe,EACf,oBAAoB,EAEpB,eAAe,EACf,UAAU,EACV,WAAW,EACX,uBAAuB,EACxB,MAAM,uBAAuB,CAAC;AAS/B,MAAM,WAAW,mBAAmB;IAClC,WAAW,IAAI,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAClD,UAAU,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACvC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACpE,UAAU,CAAC,OAAO,EAAE;QAAE,UAAU,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1F,MAAM,CAAC,OAAO,EAAE;QAAE,UAAU,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAClF,cAAc,CACZ,OAAO,EAAE,gBAAgB,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAC5C,OAAO,CAAC,4BAA4B,CAAC,CAAC;IACzC,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;CACtE;AAED,qBAAa,qBAAsB,YAAW,mBAAmB;IACnD,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,GAAG;IAE/B,WAAW,IAAI,OAAO,CAAC,yBAAyB,CAAC;IA2EjD,UAAU,IAAI,OAAO,CAAC,eAAe,CAAC;IA2DtC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAmFnE,UAAU,CAAC,OAAO,EAAE;QACxB,UAAU,EAAE,UAAU,CAAC;QACvB,MAAM,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC,eAAe,CAAC;IAiCtB,MAAM,CAAC,OAAO,EAAE;QAAE,UAAU,EAAE,UAAU,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAmDjF,cAAc,CAClB,OAAO,EAAE,gBAAgB,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,GAC5C,OAAO,CAAC,4BAA4B,CAAC;IAyHlC,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;CA+G3E"}
@@ -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"}