aiden-shared-calculations-unified 1.0.82 → 1.0.84

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 (71) hide show
  1. package/calculations/core/asset-pnl-status.js +122 -104
  2. package/calculations/core/asset-position-size.js +110 -73
  3. package/calculations/core/average-daily-pnl-all-users.js +17 -3
  4. package/calculations/core/average-daily-pnl-per-sector.js +83 -75
  5. package/calculations/core/average-daily-pnl-per-stock.js +84 -73
  6. package/calculations/core/average-daily-position-pnl.js +2 -2
  7. package/calculations/core/holding-duration-per-asset.js +24 -23
  8. package/calculations/core/instrument-price-change-1d.js +72 -82
  9. package/calculations/core/instrument-price-momentum-20d.js +66 -100
  10. package/calculations/core/long-position-per-stock.js +21 -13
  11. package/calculations/core/overall-holding-duration.js +8 -3
  12. package/calculations/core/overall-profitability-ratio.js +2 -2
  13. package/calculations/core/platform-buy-sell-sentiment.js +75 -22
  14. package/calculations/core/platform-daily-bought-vs-sold-count.js +19 -10
  15. package/calculations/core/platform-daily-ownership-delta.js +39 -15
  16. package/calculations/core/platform-ownership-per-sector.js +38 -18
  17. package/calculations/core/platform-total-positions-held.js +36 -14
  18. package/calculations/core/pnl-distribution-per-stock.js +39 -36
  19. package/calculations/core/price-metrics.js +70 -172
  20. package/calculations/core/profitability-ratio-per-sector.js +23 -29
  21. package/calculations/core/profitability-ratio-per-stock.js +20 -13
  22. package/calculations/core/profitability-skew-per-stock.js +20 -13
  23. package/calculations/core/profitable-and-unprofitable-status.js +34 -10
  24. package/calculations/core/sentiment-per-stock.js +20 -9
  25. package/calculations/core/short-position-per-stock.js +23 -37
  26. package/calculations/core/social-activity-aggregation.js +41 -115
  27. package/calculations/core/social-asset-posts-trend.js +77 -94
  28. package/calculations/core/social-event-correlation.js +87 -106
  29. package/calculations/core/social-sentiment-aggregation.js +56 -138
  30. package/calculations/core/social-top-mentioned-words.js +74 -106
  31. package/calculations/core/social-topic-interest-evolution.js +94 -94
  32. package/calculations/core/social-topic-sentiment-matrix.js +90 -74
  33. package/calculations/core/social-word-mentions-trend.js +92 -106
  34. package/calculations/core/speculator-asset-sentiment.js +63 -92
  35. package/calculations/core/speculator-danger-zone.js +77 -90
  36. package/calculations/core/speculator-distance-to-stop-loss-per-leverage.js +75 -90
  37. package/calculations/core/speculator-distance-to-tp-per-leverage.js +75 -88
  38. package/calculations/core/speculator-entry-distance-to-sl-per-leverage.js +75 -90
  39. package/calculations/core/speculator-entry-distance-to-tp-per-leverage.js +74 -89
  40. package/calculations/core/speculator-leverage-per-asset.js +62 -57
  41. package/calculations/core/speculator-leverage-per-sector.js +53 -65
  42. package/calculations/core/speculator-risk-reward-ratio-per-asset.js +71 -76
  43. package/calculations/core/speculator-stop-loss-distance-by-sector-short-long-breakdown.js +60 -81
  44. package/calculations/core/speculator-stop-loss-distance-by-ticker-short-long-breakdown.js +57 -77
  45. package/calculations/core/speculator-stop-loss-per-asset.js +43 -80
  46. package/calculations/core/speculator-take-profit-per-asset.js +45 -69
  47. package/calculations/core/speculator-tsl-per-asset.js +42 -49
  48. package/calculations/core/total-long-figures.js +19 -19
  49. package/calculations/core/total-long-per-sector.js +39 -36
  50. package/calculations/core/total-short-figures.js +19 -19
  51. package/calculations/core/total-short-per-sector.js +39 -36
  52. package/calculations/core/users-processed.js +52 -25
  53. package/calculations/gauss/cohort-capital-flow.js +38 -29
  54. package/calculations/gauss/cohort-definer.js +17 -25
  55. package/calculations/gauss/daily-dna-filter.js +10 -4
  56. package/calculations/gauss/gauss-divergence-signal.js +28 -6
  57. package/calculations/gem/cohort-momentum-state.js +113 -92
  58. package/calculations/gem/cohort-skill-definition.js +23 -53
  59. package/calculations/gem/platform-conviction-divergence.js +62 -116
  60. package/calculations/gem/quant-skill-alpha-signal.js +107 -123
  61. package/calculations/gem/skilled-cohort-flow.js +178 -167
  62. package/calculations/gem/skilled-unskilled-divergence.js +73 -113
  63. package/calculations/gem/unskilled-cohort-flow.js +176 -166
  64. package/calculations/helix/helix-contrarian-signal.js +91 -83
  65. package/calculations/helix/herd-consensus-score.js +135 -97
  66. package/calculations/helix/winner-loser-flow.js +14 -14
  67. package/calculations/pyro/risk-appetite-index.js +121 -123
  68. package/calculations/pyro/squeeze-potential.js +93 -125
  69. package/calculations/pyro/volatility-signal.js +109 -97
  70. package/package.json +9 -9
  71. package/README.MD +0 -78
@@ -1,27 +1,32 @@
1
1
  /**
2
- * @fileoverview A simple counter that increments for each user processed.
2
+ * @fileoverview Calculation (Pass 1) for total users processed.
3
+ *
4
+ * This metric is a simple counter that tracks the total number
5
+ * of users processed and the breakdown by user type (Normal/Speculator).
6
+ *
7
+ * It is a fundamental metric for sanity-checking the data stream.
3
8
  */
4
-
5
9
  class UsersProcessed {
6
10
  constructor() {
7
- this.userCount = 0;
11
+ this.totalUsers = 0;
12
+ this.normalUsers = 0;
13
+ this.speculatorUsers = 0;
14
+ this.totalValue = 0;
8
15
  }
9
16
 
10
- // --- NEW ---
11
17
  /**
12
18
  * Statically defines all metadata for the manifest builder.
13
19
  */
14
20
  static getMetadata() {
15
21
  return {
16
22
  type: 'standard',
17
- rootDataDependencies: ['portfolio'], // Needs at least one doc to run
23
+ rootDataDependencies: ['portfolio'], // Needs summary
18
24
  isHistorical: false,
19
25
  userType: 'all',
20
- category: 'core_sanity' // Based on Computation_documentation.md
26
+ category: 'core_metrics' // A fundamental metric
21
27
  };
22
28
  }
23
29
 
24
- // --- NEW ---
25
30
  /**
26
31
  * Statically declare dependencies.
27
32
  */
@@ -31,41 +36,63 @@ class UsersProcessed {
31
36
 
32
37
  /**
33
38
  * Defines the output schema for this calculation.
34
- * @returns {object} JSON Schema object
35
39
  */
36
40
  static getSchema() {
37
41
  return {
38
42
  "type": "object",
39
- "description": "A simple counter for the total number of users processed.",
43
+ "description": "Tracks the total count of users processed and their type.",
40
44
  "properties": {
41
- "rawTotalUsersProcessed": {
42
- "type": "number",
43
- "description": "The total count of users processed by the computation system."
44
- }
45
+ "total_users": { "type": "number" },
46
+ "normal_users": { "type": "number" },
47
+ "speculator_users": { "type": "number" },
48
+ "total_portfolio_value": { "type": "number" }
45
49
  },
46
- "required": ["rawTotalUsersProcessed"]
50
+ "required": ["total_users", "normal_users", "speculator_users", "total_portfolio_value"]
47
51
  };
48
52
  }
49
53
 
50
- process(portfolioData, yesterdayPortfolio, userId, context) {
51
- this.userCount++;
54
+ // --- UPDATED SIGNATURE ---
55
+ process(
56
+ todayPortfolio,
57
+ yesterdayPortfolio,
58
+ userId,
59
+ context,
60
+ todayInsights,
61
+ yesterdayInsights,
62
+ todaySocialPostInsights,
63
+ yesterdaySocialPostInsights,
64
+ todayHistory
65
+ ) {
66
+ this.totalUsers++;
67
+
68
+ // Use the userType from the context, which is set by the orchestrator
69
+ if (context.userType === 'speculator') {
70
+ this.speculatorUsers++;
71
+ } else {
72
+ this.normalUsers++;
73
+ }
74
+
75
+ // --- UPDATED ---
76
+ const portfolioValue = todayPortfolio?.Summary?.PortfolioValue;
77
+ if (typeof portfolioValue === 'number') {
78
+ this.totalValue += portfolioValue;
79
+ }
52
80
  }
53
81
 
54
82
  getResult() {
55
- if (this.userCount === 0) {
56
- // Even if 0, we should return the defined schema shape
57
- return {
58
- rawTotalUsersProcessed: 0
59
- };
60
- }
61
- // FIX: Rename the key to use the 'raw' prefix for correct aggregation.
62
83
  return {
63
- rawTotalUsersProcessed: this.userCount
84
+ total_users: this.totalUsers,
85
+ normal_users: this.normalUsers,
86
+ speculator_users: this.speculatorUsers,
87
+ total_portfolio_value: this.totalValue
64
88
  };
65
89
  }
66
90
 
67
91
  reset() {
68
- this.userCount = 0;
92
+ this.totalUsers = 0;
93
+ this.normalUsers = 0;
94
+ this.speculatorUsers = 0;
95
+ this.totalValue = 0;
69
96
  }
70
97
  }
71
98
 
@@ -12,9 +12,11 @@
12
12
  * - Added dependency on 'instrument-price-change-1d' (Pass 1 meta calc).
13
13
  * - Price adjustment logic now uses the reliable 1-day price change
14
14
  * from the new dependency.
15
+ * - **FIXED:** getResult() was wrapping the ticker map in an "assets" object,
16
+ * causing a schema validation failure in the test harness.
15
17
  * --------------------------
16
18
  */
17
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
19
+ // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
18
20
 
19
21
 
20
22
  class CohortCapitalFlow {
@@ -23,7 +25,8 @@ class CohortCapitalFlow {
23
25
  this.cohortFlows = new Map();
24
26
  // { [userId]: "cohortName" }
25
27
  this.cohortMap = new Map();
26
- this.mappings = null;
28
+ // --- STANDARD 0: RENAMED ---
29
+ this.tickerMap = null;
27
30
  this.dependenciesLoaded = false;
28
31
 
29
32
  // --- NEW ---
@@ -35,7 +38,6 @@ class CohortCapitalFlow {
35
38
  * Defines the output schema for this calculation.
36
39
  */
37
40
  static getSchema() {
38
- // ... (Schema remains unchanged) ...
39
41
  const flowSchema = {
40
42
  "type": "object",
41
43
  "properties": {
@@ -68,8 +70,6 @@ class CohortCapitalFlow {
68
70
  static getMetadata() {
69
71
  return {
70
72
  type: 'standard',
71
- // --- REVISED ---
72
- // Removed 'insights' as it's no longer needed for price.
73
73
  rootDataDependencies: ['portfolio', 'history'],
74
74
  isHistorical: true, // Needs T-1 portfolio for flow
75
75
  userType: 'all',
@@ -83,13 +83,19 @@ class CohortCapitalFlow {
83
83
  static getDependencies() {
84
84
  return [
85
85
  'cohort-definer', // from gauss (Pass 2)
86
- // --- REVISED ---
87
86
  'instrument-price-change-1d' // from core (Pass 1)
88
87
  ];
89
88
  }
90
89
 
91
- _getPortfolioPositions(portfolio) {
92
- return portfolio?.AggregatedPositions;
90
+ _getPortfolioPositions(portfolio) {
91
+ // FIX: Check for Normal User (AggregatedPositions) OR Speculator (PublicPositions)
92
+ if (portfolio?.AggregatedPositions) {
93
+ return portfolio.AggregatedPositions; // Normal User
94
+ }
95
+ if (portfolio?.PublicPositions) {
96
+ return portfolio.PublicPositions; // Speculator User
97
+ }
98
+ return [];
93
99
  }
94
100
 
95
101
  _initFlowData(cohortName, instrumentId) {
@@ -124,7 +130,6 @@ class CohortCapitalFlow {
124
130
  }
125
131
 
126
132
  // 2. Load Price Change Data
127
- // --- REVISED ---
128
133
  this.priceChangeMap = fetchedDependencies['instrument-price-change-1d'] || {};
129
134
 
130
135
  this.dependenciesLoaded = true;
@@ -133,8 +138,9 @@ class CohortCapitalFlow {
133
138
  process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
134
139
  this._loadDependencies(fetchedDependencies);
135
140
 
136
- if (!this.mappings) {
137
- this.mappings = context.mappings;
141
+ // --- STANDARD 0: FIXED ---
142
+ if (!this.tickerMap) {
143
+ this.tickerMap = context.instrumentToTicker;
138
144
  }
139
145
 
140
146
  const cohortName = this.cohortMap.get(userId);
@@ -153,11 +159,14 @@ class CohortCapitalFlow {
153
159
  return; // Must have AggregatedPositions for both days
154
160
  }
155
161
 
156
- // --- REVISED ---
157
- // We no longer need insightsMap
158
162
  if (!this.priceChangeMap) {
159
163
  return; // Cannot calculate price-adjusted flow
160
164
  }
165
+
166
+ // --- STANDARD 0: Check must be done before use ---
167
+ if (!this.tickerMap) {
168
+ return; // Cannot map instruments to tickers
169
+ }
161
170
 
162
171
  const yPosMap = new Map(yPos.map(p => [p.InstrumentID, p]));
163
172
  const tPosMap = new Map(tPos.map(p => [p.InstrumentID, p]));
@@ -178,14 +187,12 @@ class CohortCapitalFlow {
178
187
  if (yInvested > 0) {
179
188
  asset.total_invested_yesterday += yInvested;
180
189
 
181
- // --- REVISED ---
182
- // Get the 1-day price change from our new dependency
183
- const ticker = this.mappings.instrumentToTicker[instrumentId];
190
+ // --- STANDARD 0: SIMPLIFIED ---
191
+ const ticker = this.tickerMap[instrumentId];
184
192
  const yPriceChange_pct = (ticker && this.priceChangeMap[ticker])
185
193
  ? this.priceChangeMap[ticker].price_change_1d_pct
186
194
  : 0;
187
195
 
188
- // Convert from percentage (5.5) to decimal (0.055)
189
196
  const yPriceChange_decimal = (yPriceChange_pct || 0) / 100.0;
190
197
 
191
198
  asset.price_change_yesterday += yPriceChange_decimal * yInvested; // Weighted sum
@@ -196,18 +203,22 @@ class CohortCapitalFlow {
196
203
  }
197
204
  }
198
205
 
206
+ // --- THIS IS THE FIX ---
199
207
  async getResult() {
200
- if (!this.mappings) {
201
- this.mappings = await loadInstrumentMappings();
208
+ // --- STANDARD 0: REMOVED forbidden data load ---
209
+
210
+ // Failsafe check
211
+ if (!this.tickerMap) {
212
+ return {}; // process() must run first to set mappings
202
213
  }
203
214
 
204
215
  const finalResult = {};
205
216
 
206
217
  for (const [cohortName, assetMap] of this.cohortFlows.entries()) {
207
- // --- REVISED: Initialize cohortAssets as an object ---
208
218
  const cohortAssets = {};
209
219
  for (const [instrumentId, data] of assetMap.entries()) {
210
- const ticker = this.mappings.instrumentToTicker[instrumentId];
220
+ // --- STANDARD 0: SIMPLIFIED ---
221
+ const ticker = this.tickerMap[instrumentId];
211
222
  if (!ticker) continue;
212
223
 
213
224
  const { total_invested_yesterday, total_invested_today, price_change_yesterday } = data;
@@ -231,23 +242,21 @@ class CohortCapitalFlow {
231
242
  };
232
243
  }
233
244
  }
234
- // --- REVISED: Match schema { "cohortName": { "assets": { ... } } } ---
235
- // This was a bug in your original file. The schema expected an object
236
- // with an 'assets' key, but the code was returning the map directly.
237
- finalResult[cohortName] = { assets: cohortAssets };
245
+ // Was: finalResult[cohortName] = { assets: cohortAssets };
246
+ // Is:
247
+ finalResult[cohortName] = cohortAssets;
238
248
  }
239
249
 
240
- // --- REVISED: The schema for this calc shows the output is NOT sharded. ---
241
- // The return should be the final object.
242
250
  return finalResult;
243
251
  }
252
+ // --- END FIX ---
244
253
 
245
254
  reset() {
246
255
  this.cohortFlows.clear();
247
256
  this.cohortMap.clear();
248
- this.mappings = null;
257
+ // --- STANDARD 0: RENAMED ---
258
+ this.tickerMap = null;
249
259
  this.dependenciesLoaded = false;
250
- // --- NEW ---
251
260
  this.priceChangeMap = null;
252
261
  }
253
262
  }
@@ -8,7 +8,7 @@
8
8
  * It then buckets these users into our final, named sub-cohorts
9
9
  * (e.g., "Smart Investors", "FOMO Chasers").
10
10
  */
11
- const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
11
+ // --- STANDARD 0: REMOVED require('../../utils/sector_mapping_provider') ---
12
12
 
13
13
 
14
14
  class CohortDefiner {
@@ -18,7 +18,8 @@ class CohortDefiner {
18
18
  this.dumbVectors = []; // [ { userId, skill, time, fomo, bagholder }, ... ]
19
19
 
20
20
  this.momentumData = null;
21
- this.mappings = null;
21
+ // --- STANDARD 0: RENAMED ---
22
+ this.tickerMap = null;
22
23
  this.cohortIdSets = null; // { smart: Set, dumb: Set }
23
24
  }
24
25
 
@@ -60,7 +61,6 @@ class CohortDefiner {
60
61
  "fomo_chasers": cohortSchema,
61
62
  "patient_losers": cohortSchema,
62
63
  "fomo_bagholders": cohortSchema,
63
- // --- FIX [PROBLEM 9]: Add uncategorized bucket ---
64
64
  "uncategorized_smart": cohortSchema,
65
65
  "uncategorized_dumb": cohortSchema
66
66
  },
@@ -77,14 +77,12 @@ class CohortDefiner {
77
77
  const dnaFilterData = fetchedDependencies['daily-dna-filter'];
78
78
  this.momentumData = fetchedDependencies['instrument-price-momentum-20d'];
79
79
 
80
- // --- FIX [PROBLEM 6]: Validate dependency content, not just existence ---
81
80
  if (dnaFilterData && dnaFilterData.smart_cohort_ids && dnaFilterData.dumb_cohort_ids) {
82
81
  this.cohortIdSets = {
83
82
  smart: new Set(dnaFilterData.smart_cohort_ids),
84
83
  dumb: new Set(dnaFilterData.dumb_cohort_ids)
85
84
  };
86
85
  } else {
87
- // Initialize with empty sets if dependency is missing or malformed
88
86
  this.cohortIdSets = {
89
87
  smart: new Set(),
90
88
  dumb: new Set()
@@ -93,9 +91,9 @@ class CohortDefiner {
93
91
  }
94
92
 
95
93
  _getFomoScore(todayPortfolio, yesterdayPortfolio) {
96
- if (!this.mappings) return 0;
94
+ // --- STANDARD 0: UPDATED CHECK ---
95
+ if (!this.tickerMap) return 0;
97
96
 
98
- // --- FIX [PROBLEM 4 related]: Ensure momentum data is loaded ---
99
97
  if (!this.momentumData) {
100
98
  return 0; // Cannot calculate FOMO without momentum data
101
99
  }
@@ -107,8 +105,8 @@ class CohortDefiner {
107
105
  let fomoSum = 0;
108
106
  let count = 0;
109
107
  for (const pos of newPositions) {
110
- const ticker = this.mappings.instrumentToTicker[pos.InstrumentID];
111
- // --- FIX [PROBLEM 4 related]: Check momentumData[ticker] exists ---
108
+ // --- STANDARD 0: SIMPLIFIED ---
109
+ const ticker = this.tickerMap[pos.InstrumentID];
112
110
  if (ticker && this.momentumData[ticker]) {
113
111
  fomoSum += this.momentumData[ticker].momentum_20d_pct || 0;
114
112
  count++;
@@ -118,7 +116,8 @@ class CohortDefiner {
118
116
  }
119
117
 
120
118
  _getBagholderScore(todayPortfolio) {
121
- const openPositions = todayPortfolio?.AggregatedPositions || [];
119
+ // FIX: Only Speculator data (PublicPositions) has the OpenDateTime field needed for this calculation.
120
+ const openPositions = todayPortfolio?.PublicPositions || [];
122
121
  if (openPositions.length === 0) return 0;
123
122
 
124
123
  let durationSum = 0;
@@ -126,8 +125,7 @@ class CohortDefiner {
126
125
  const now = new Date();
127
126
 
128
127
  for (const pos of openPositions) {
129
- // 'NetProfit' is a % P&L, e.g., -0.20 for -20%
130
- if (pos.NetProfit < -0.20) {
128
+ if (pos.NetProfit < -20) {
131
129
  try {
132
130
  const openDate = new Date(pos.OpenDateTime);
133
131
  const durationMs = now.getTime() - openDate.getTime();
@@ -144,8 +142,10 @@ class CohortDefiner {
144
142
 
145
143
  process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
146
144
  this._loadDependencies(fetchedDependencies);
147
- if (!this.mappings) {
148
- this.mappings = context.mappings;
145
+
146
+ // --- STANDARD 0: FIXED ---
147
+ if (!this.tickerMap) {
148
+ this.tickerMap = context.instrumentToTicker;
149
149
  }
150
150
 
151
151
  const isSmart = this.cohortIdSets.smart.has(userId);
@@ -155,9 +155,7 @@ class CohortDefiner {
155
155
  return; // Not in our extreme cohorts
156
156
  }
157
157
 
158
- // --- Re-calculate full DNA for this filtered user ---
159
-
160
- // 1. Get Long-Term (Stable) Traits
158
+ // FIX: Access history data from the nested property provided by the fixed worker.js
161
159
  const history = todayPortfolio?.history?.all;
162
160
  if (!history) return;
163
161
 
@@ -168,7 +166,6 @@ class CohortDefiner {
168
166
  const lt_skill = (winRate * avgWin) - (lossRate * avgLoss);
169
167
  const lt_time = history.avgHoldingTimeInMinutes || 0;
170
168
 
171
- // 2. Get Short-Term (Noisy) Traits
172
169
  const st_fomo = this._getFomoScore(todayPortfolio, yesterdayPortfolio);
173
170
  const st_bagholder = this._getBagholderScore(todayPortfolio);
174
171
 
@@ -185,7 +182,6 @@ class CohortDefiner {
185
182
  if (vectors.length === 0) return 0;
186
183
  const sorted = vectors.map(v => v[key]).sort((a, b) => a - b);
187
184
  const mid = Math.floor(sorted.length / 2);
188
- // Handle even-length array by taking average of middle two
189
185
  if (sorted.length % 2 === 0 && sorted.length > 0) {
190
186
  return (sorted[mid - 1] + sorted[mid]) / 2;
191
187
  }
@@ -197,7 +193,6 @@ class CohortDefiner {
197
193
  const assignedSmart = new Set();
198
194
  const assignedDumb = new Set();
199
195
 
200
- // 1. Process Smart Cohort
201
196
  const smart_median_time = this._getMedian(this.smartVectors, 'time');
202
197
 
203
198
  cohorts['smart_investors'] = this.smartVectors
@@ -208,12 +203,10 @@ class CohortDefiner {
208
203
  .filter(u => u.time < smart_median_time)
209
204
  .map(u => { assignedSmart.add(u.userId); return u.userId; });
210
205
 
211
- // --- FIX [PROBLEM 9]: Add uncategorized bucket ---
212
206
  cohorts['uncategorized_smart'] = this.smartVectors
213
207
  .filter(u => !assignedSmart.has(u.userId))
214
208
  .map(u => u.userId);
215
209
 
216
- // 2. Process Dumb Cohort
217
210
  const dumb_median_fomo = this._getMedian(this.dumbVectors, 'fomo');
218
211
  const dumb_median_bag = this._getMedian(this.dumbVectors, 'bagholder');
219
212
 
@@ -229,12 +222,10 @@ class CohortDefiner {
229
222
  .filter(u => u.fomo >= dumb_median_fomo && u.bagholder >= dumb_median_bag)
230
223
  .map(u => { assignedDumb.add(u.userId); return u.userId; });
231
224
 
232
- // --- FIX [PROBLEM 9]: Add uncategorized bucket ---
233
225
  cohorts['uncategorized_dumb'] = this.dumbVectors
234
226
  .filter(u => !assignedDumb.has(u.userId))
235
227
  .map(u => u.userId);
236
228
 
237
- // Output is a compact map of cohort_name -> [userIds]
238
229
  return cohorts;
239
230
  }
240
231
 
@@ -242,7 +233,8 @@ class CohortDefiner {
242
233
  this.smartVectors = [];
243
234
  this.dumbVectors = [];
244
235
  this.momentumData = null;
245
- this.mappings = null;
236
+ // --- STANDARD 0: RENAMED ---
237
+ this.tickerMap = null;
246
238
  this.cohortIdSets = null;
247
239
  }
248
240
  }
@@ -21,8 +21,9 @@ class DailyDnaFilter {
21
21
  static getMetadata() {
22
22
  return {
23
23
  type: 'standard',
24
- // This calc only needs the 'history' doc,
25
- // which is passed inside the portfolio object.
24
+ // This calc only needs the 'history' doc.
25
+ // As per the test harness, this means 'todayPortfolio'
26
+ // in process() *is* the history object.
26
27
  rootDataDependencies: ['history'],
27
28
  isHistorical: false, // Reads today's history snapshot
28
29
  userType: 'all',
@@ -70,8 +71,13 @@ class DailyDnaFilter {
70
71
  * @param {string} userId - The user's ID.
71
72
  */
72
73
  process(todayPortfolio, yesterdayPortfolio, userId) {
73
- // 1. Get Long-Term (Stable) Traits from history
74
- const history = todayPortfolio?.history?.all;
74
+
75
+ // ---
76
+ // FIX: 'todayPortfolio' *is* the history object because
77
+ // getMetadata() only requests ['history'].
78
+ // The path is 'todayPortfolio.all', not 'todayPortfolio.history.all'.
79
+ // ---
80
+ const history = todayPortfolio?.all;
75
81
  if (!history || !history.totalTrades || history.totalTrades < 20) {
76
82
  return; // Not enough history for a reliable score
77
83
  }
@@ -6,9 +6,20 @@
6
6
  *
7
7
  * It consumes the daily flows from all defined cohorts (Pass 3)
8
8
  * and aggregates them into a final, actionable signal.
9
+ *
10
+ * --- FIX ---
11
+ * - Aligned data access with the corrected schema of its
12
+ * dependency, 'cohort-capital-flow'.
13
+ * - It now reads 'cohortFlows[cohortName]' directly, instead of
14
+ * the non-existent 'cohortFlows[cohortName].assets'.
9
15
  */
10
16
  class GaussDivergenceSignal {
11
17
 
18
+ // --- STANDARD 2: ADDED ---
19
+ constructor() {
20
+ this.result = {};
21
+ }
22
+
12
23
  /**
13
24
  * Defines the output schema for this calculation.
14
25
  * @returns {object} JSON Schema object
@@ -84,14 +95,16 @@ class GaussDivergenceSignal {
84
95
  /**
85
96
  * This is a 'meta' calculation. It runs once.
86
97
  */
87
- async process(dateStr, dependencies, config, fetchedDependencies) {
98
+ // --- THIS IS THE FIX ---
99
+ async process(dateStr, rootData, dependencies, config, fetchedDependencies) {
88
100
  const { logger } = dependencies;
89
101
 
90
102
  const cohortFlows = fetchedDependencies['cohort-capital-flow'];
91
103
 
92
104
  if (!cohortFlows) {
93
105
  logger.log('WARN', `[gauss/gauss-divergence-signal] Missing dependency 'cohort-capital-flow' for ${dateStr}.`);
94
- return {};
106
+ this.result = {};
107
+ return;
95
108
  }
96
109
 
97
110
  const SMART_COHORTS = ['smart_investors', 'smart_scalpers'];
@@ -105,9 +118,8 @@ class GaussDivergenceSignal {
105
118
  const isDumb = DUMB_COHORTS.includes(cohortName);
106
119
  if (!isSmart && !isDumb) continue;
107
120
 
108
- // --- REVISED ---
109
- // Read from the 'assets' property, which contains the map of tickers
110
- const assets = cohortFlows[cohortName]?.assets;
121
+ // Was: const assets = cohortFlows[cohortName]?.assets;
122
+ const assets = cohortFlows[cohortName]; // This is now the ticker map
111
123
  if (!assets) continue;
112
124
 
113
125
  for (const [ticker, data] of Object.entries(assets)) {
@@ -115,6 +127,7 @@ class GaussDivergenceSignal {
115
127
  blendedFlows.set(ticker, { smart: 0, dumb: 0 });
116
128
  }
117
129
 
130
+ // This logic remains correct, as it reads the inner flow object
118
131
  const flow = data.net_flow_contribution || 0;
119
132
 
120
133
  if (isSmart) {
@@ -124,6 +137,7 @@ class GaussDivergenceSignal {
124
137
  }
125
138
  }
126
139
  }
140
+ // --- END FIX ---
127
141
 
128
142
  // 2. Calculate final signal (logic unchanged)
129
143
  const result = {};
@@ -145,7 +159,15 @@ class GaussDivergenceSignal {
145
159
  };
146
160
  }
147
161
 
148
- return result;
162
+ this.result = result;
163
+ }
164
+
165
+ async getResult(fetchedDependencies) {
166
+ return this.result;
167
+ }
168
+
169
+ reset() {
170
+ this.result = {};
149
171
  }
150
172
  }
151
173