@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,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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Canonical domain taxonomy seed.
3
+ *
4
+ * The slugs below are the only values analytics queries should group by.
5
+ * `markets.category` and `markets.topics` (the existing free-form fields in
6
+ * SQLite) are mapped into these slugs by `backfillMarketDomains`.
7
+ *
8
+ * To add a new domain: append to CANONICAL_DOMAINS, run the indexer once.
9
+ * To remap topics → domain: edit TOPIC_TO_DOMAIN and re-run the backfill.
10
+ */
11
+ import type { SQL } from 'bun';
12
+ export interface CanonicalDomain {
13
+ readonly slug: string;
14
+ readonly name: string;
15
+ readonly description: string;
16
+ readonly sortOrder: number;
17
+ }
18
+ export declare const CANONICAL_DOMAINS: readonly CanonicalDomain[];
19
+ /**
20
+ * Map a free-form topic or category string to a canonical domain slug.
21
+ * Returns 'other' if no rule matches.
22
+ */
23
+ export declare function classifyTopic(raw: string): string;
24
+ /**
25
+ * Insert canonical domains, idempotent. Updates name/description/sort_order
26
+ * if a slug already exists.
27
+ */
28
+ export declare function seedCanonicalDomains(sql: SQL): Promise<void>;
29
+ //# sourceMappingURL=seed-domains.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-domains.d.ts","sourceRoot":"","sources":["../../src/warehouse/seed-domains.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE/B,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,iBAAiB,EAAE,SAAS,eAAe,EAmE9C,CAAC;AAqHX;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAkBjD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAYlE"}
@@ -0,0 +1,223 @@
1
+ export const CANONICAL_DOMAINS = [
2
+ {
3
+ slug: 'politics',
4
+ name: 'Politics',
5
+ description: 'Elections, legislation, polls, and policy outcomes.',
6
+ sortOrder: 10,
7
+ },
8
+ {
9
+ slug: 'economics',
10
+ name: 'Economics',
11
+ description: 'Macroeconomic indicators, inflation, GDP, employment, rates.',
12
+ sortOrder: 20,
13
+ },
14
+ {
15
+ slug: 'crypto',
16
+ name: 'Crypto',
17
+ description: 'On-chain metrics, token prices, protocol-level outcomes.',
18
+ sortOrder: 30,
19
+ },
20
+ {
21
+ slug: 'weather',
22
+ name: 'Weather & Climate',
23
+ description: 'Temperature, precipitation, storm tracks, climate indicators.',
24
+ sortOrder: 40,
25
+ },
26
+ {
27
+ slug: 'sports',
28
+ name: 'Sports',
29
+ description: 'Match outcomes, season standings, performance metrics.',
30
+ sortOrder: 50,
31
+ },
32
+ {
33
+ slug: 'geopolitics',
34
+ name: 'Geopolitics',
35
+ description: 'International relations, conflicts, treaties, diplomatic outcomes.',
36
+ sortOrder: 60,
37
+ },
38
+ {
39
+ slug: 'science',
40
+ name: 'Science & Tech Research',
41
+ description: 'Scientific results, peer-reviewed claims, technology breakthroughs.',
42
+ sortOrder: 70,
43
+ },
44
+ {
45
+ slug: 'technology',
46
+ name: 'Technology & Industry',
47
+ description: 'Product launches, company outcomes, industry events.',
48
+ sortOrder: 80,
49
+ },
50
+ {
51
+ slug: 'culture',
52
+ name: 'Culture',
53
+ description: 'Awards, entertainment, social trends, cultural events.',
54
+ sortOrder: 90,
55
+ },
56
+ {
57
+ slug: 'entertainment',
58
+ name: 'Entertainment & Media',
59
+ description: 'Box office, streaming, music charts, media releases.',
60
+ sortOrder: 100,
61
+ },
62
+ {
63
+ slug: 'other',
64
+ name: 'Other',
65
+ description: 'Markets that have not been mapped to a specific domain yet.',
66
+ sortOrder: 9999,
67
+ },
68
+ ];
69
+ /**
70
+ * Lowercase substring → canonical slug. The first match wins. Order matters:
71
+ * put more specific matches first.
72
+ *
73
+ * Applied to (a) `markets.category` (lowercased) and (b) each entry in
74
+ * `markets.topics` (lowercased) — see `backfillMarketDomains`.
75
+ */
76
+ const TOPIC_TO_DOMAIN = [
77
+ // ── politics ──
78
+ ['election', 'politics'],
79
+ ['president', 'politics'],
80
+ ['senate', 'politics'],
81
+ ['congress', 'politics'],
82
+ ['parliament', 'politics'],
83
+ ['vote', 'politics'],
84
+ ['poll', 'politics'],
85
+ ['republican', 'politics'],
86
+ ['democrat', 'politics'],
87
+ ['politic', 'politics'],
88
+ // ── economics ──
89
+ ['inflation', 'economics'],
90
+ ['cpi', 'economics'],
91
+ ['ppi', 'economics'],
92
+ ['gdp', 'economics'],
93
+ ['unemployment', 'economics'],
94
+ ['jobs', 'economics'],
95
+ ['payroll', 'economics'],
96
+ ['fed', 'economics'],
97
+ ['interest rate', 'economics'],
98
+ ['fomc', 'economics'],
99
+ ['recession', 'economics'],
100
+ ['economic', 'economics'],
101
+ ['econ', 'economics'],
102
+ // ── crypto ──
103
+ ['bitcoin', 'crypto'],
104
+ ['ethereum', 'crypto'],
105
+ ['btc', 'crypto'],
106
+ ['eth', 'crypto'],
107
+ ['solana', 'crypto'],
108
+ ['starknet', 'crypto'],
109
+ ['stablecoin', 'crypto'],
110
+ ['defi', 'crypto'],
111
+ ['token', 'crypto'],
112
+ ['crypto', 'crypto'],
113
+ // ── weather ──
114
+ ['hurricane', 'weather'],
115
+ ['typhoon', 'weather'],
116
+ ['storm', 'weather'],
117
+ ['temperature', 'weather'],
118
+ ['snowfall', 'weather'],
119
+ ['rainfall', 'weather'],
120
+ ['climate', 'weather'],
121
+ ['weather', 'weather'],
122
+ // ── sports ──
123
+ ['nba', 'sports'],
124
+ ['nfl', 'sports'],
125
+ ['mlb', 'sports'],
126
+ ['nhl', 'sports'],
127
+ ['soccer', 'sports'],
128
+ ['football', 'sports'],
129
+ ['premier league', 'sports'],
130
+ ['champions league', 'sports'],
131
+ ['world cup', 'sports'],
132
+ ['olympic', 'sports'],
133
+ ['tennis', 'sports'],
134
+ ['ufc', 'sports'],
135
+ ['mma', 'sports'],
136
+ ['sport', 'sports'],
137
+ // ── geopolitics ──
138
+ ['ukraine', 'geopolitics'],
139
+ ['russia', 'geopolitics'],
140
+ ['china', 'geopolitics'],
141
+ ['nato', 'geopolitics'],
142
+ ['un security', 'geopolitics'],
143
+ ['ceasefire', 'geopolitics'],
144
+ ['sanction', 'geopolitics'],
145
+ ['treaty', 'geopolitics'],
146
+ ['geopolitic', 'geopolitics'],
147
+ // ── science ──
148
+ ['nasa', 'science'],
149
+ ['spacex', 'science'],
150
+ ['rocket', 'science'],
151
+ ['vaccine', 'science'],
152
+ ['nobel', 'science'],
153
+ ['physics', 'science'],
154
+ ['biology', 'science'],
155
+ ['research', 'science'],
156
+ ['science', 'science'],
157
+ // ── technology ──
158
+ ['ai model', 'technology'],
159
+ ['gpt', 'technology'],
160
+ ['claude', 'technology'],
161
+ ['gemini', 'technology'],
162
+ ['ipo', 'technology'],
163
+ ['layoff', 'technology'],
164
+ ['acquisition', 'technology'],
165
+ ['apple', 'technology'],
166
+ ['google', 'technology'],
167
+ ['microsoft', 'technology'],
168
+ ['tech', 'technology'],
169
+ ['technology', 'technology'],
170
+ // ── culture / entertainment ──
171
+ ['oscar', 'entertainment'],
172
+ ['grammy', 'entertainment'],
173
+ ['emmy', 'entertainment'],
174
+ ['box office', 'entertainment'],
175
+ ['netflix', 'entertainment'],
176
+ ['streaming', 'entertainment'],
177
+ ['movie', 'entertainment'],
178
+ ['album', 'entertainment'],
179
+ ['entertain', 'entertainment'],
180
+ ['celebrity', 'culture'],
181
+ ['culture', 'culture'],
182
+ ];
183
+ /**
184
+ * Map a free-form topic or category string to a canonical domain slug.
185
+ * Returns 'other' if no rule matches.
186
+ */
187
+ export function classifyTopic(raw) {
188
+ const lower = raw.toLowerCase().trim();
189
+ if (lower.length === 0) {
190
+ return 'other';
191
+ }
192
+ // Direct slug match (already canonical)
193
+ for (const domain of CANONICAL_DOMAINS) {
194
+ if (lower === domain.slug || lower === domain.name.toLowerCase()) {
195
+ return domain.slug;
196
+ }
197
+ }
198
+ // Substring rules
199
+ for (const [needle, slug] of TOPIC_TO_DOMAIN) {
200
+ if (lower.includes(needle)) {
201
+ return slug;
202
+ }
203
+ }
204
+ return 'other';
205
+ }
206
+ /**
207
+ * Insert canonical domains, idempotent. Updates name/description/sort_order
208
+ * if a slug already exists.
209
+ */
210
+ export async function seedCanonicalDomains(sql) {
211
+ for (const domain of CANONICAL_DOMAINS) {
212
+ await sql `
213
+ INSERT INTO domains (slug, name, description, sort_order, updated_at)
214
+ VALUES (${domain.slug}, ${domain.name}, ${domain.description}, ${domain.sortOrder}, now())
215
+ ON CONFLICT (slug) DO UPDATE SET
216
+ name = EXCLUDED.name,
217
+ description = EXCLUDED.description,
218
+ sort_order = EXCLUDED.sort_order,
219
+ updated_at = now()
220
+ `;
221
+ }
222
+ }
223
+ //# sourceMappingURL=seed-domains.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-domains.js","sourceRoot":"","sources":["../../src/warehouse/seed-domains.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@the-situation/indexer",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "ETL indexer and REST/WS API for The Situation prediction markets",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",