@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.
- package/README.md +43 -4
- 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 +11 -11
- 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/health.d.ts.map +1 -1
- package/dist/api/routes/health.js +2 -0
- package/dist/api/routes/health.js.map +1 -1
- 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/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 +17 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +39 -3
- 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/index.js +64 -8
- 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/layers/VoyagerClientLive.d.ts +2 -1
- package/dist/layers/VoyagerClientLive.d.ts.map +1 -1
- package/dist/layers/VoyagerClientLive.js +89 -55
- package/dist/layers/VoyagerClientLive.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/services/VoyagerRateLimit.d.ts +11 -8
- package/dist/services/VoyagerRateLimit.d.ts.map +1 -1
- package/dist/services/VoyagerRateLimit.js +51 -26
- package/dist/services/VoyagerRateLimit.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/api.d.ts +2 -0
- package/dist/types/api.d.ts.map +1 -1
- 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,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"}
|