aiden-shared-calculations-unified 1.0.148 → 1.0.149

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.
@@ -5,7 +5,7 @@ class AumLeaderboard {
5
5
  static getMetadata() {
6
6
  return {
7
7
  type: 'meta',
8
- category: 'popular-investor',
8
+ category: 'analytics',
9
9
  // We only need the rankings root data
10
10
  rootDataDependencies: ['rankings'],
11
11
  mandatoryRoots: ['rankings'],
@@ -58,9 +58,8 @@ class AumLeaderboard {
58
58
 
59
59
  // 4. Output Global Result
60
60
  this.results = rankedResult;
61
- }
62
-
63
- async getResult() {
61
+
62
+ // CRITICAL FIX: MetaExecutor requires the data to be returned
64
63
  return this.results;
65
64
  }
66
65
  }
@@ -1,42 +1,51 @@
1
- class GlobalAumAggregator {
1
+ /**
2
+ * @fileoverview Aggregates PI Asset AUM over the last 30 days.
3
+ */
4
+ class GlobalAumPerAsset30D {
2
5
  static getMetadata() {
3
6
  return {
4
7
  type: 'meta',
5
8
  category: 'analytics',
6
9
  rootDataDependencies: [],
7
- // No dependencySeries needed! We just need Today's results.
10
+ // No dependencySeries needed! We just need Today's results via getDependencies.
8
11
  schedule: { type: 'DAILY' }
9
12
  };
10
13
  }
11
14
 
12
15
  static getDependencies() {
13
16
  // Force running AFTER the Standard comp
14
- return ['PIDailyRollingAUM'];
17
+ // CRITICAL: Must match the File Name of the standard computation
18
+ return ['PIDailyAssetAUM'];
15
19
  }
16
20
 
17
21
  async process(context) {
18
- // We access the STANDARD dependencies (Today's snapshot)
19
- // Because of getDependencies(), system injected this into context.dependencies
20
- const allUserResults = context.computed['PIDailyRollingAUM'];
22
+ // Access the results injected by getDependencies()
23
+ // CRITICAL: Must match the File Name
24
+ const allUserResults = context.computed['PIDailyAssetAUM'];
21
25
 
22
26
  const globalAverages = {};
23
27
  let userCount = 0;
24
28
 
25
- Object.values(allUserResults).forEach(userOutput => {
26
- // userOutput is the object we saved above: { dailyMap, averageMap }
27
- const avgMap = userOutput.averageMap;
28
- if (!avgMap) return;
29
+ if (allUserResults) {
30
+ Object.values(allUserResults).forEach(userOutput => {
31
+ // userOutput is: { dailyMap, averageMap }
32
+ const avgMap = userOutput.averageMap;
33
+ if (!avgMap) return;
29
34
 
30
- userCount++;
31
- Object.entries(avgMap).forEach(([ticker, avgVal]) => {
32
- if (!globalAverages[ticker]) globalAverages[ticker] = 0;
33
- globalAverages[ticker] += avgVal;
35
+ userCount++;
36
+ Object.entries(avgMap).forEach(([ticker, avgVal]) => {
37
+ if (!globalAverages[ticker]) globalAverages[ticker] = 0;
38
+ globalAverages[ticker] += avgVal;
39
+ });
34
40
  });
35
- });
41
+ }
36
42
 
37
43
  // Result: The Sum of everyone's 30-Day Average Allocations
38
44
  this.results = globalAverages;
45
+
46
+ // CRITICAL FIX: MetaExecutor requires the data to be returned
47
+ return this.results;
39
48
  }
49
+ }
40
50
 
41
- async getResult() { return this.results; }
42
- }
51
+ module.exports = GlobalAumPerAsset30D;
@@ -1,26 +1,30 @@
1
- class PIDailyRollingAUM {
1
+ /**
2
+ * @fileoverview Calculates the $ AUM allocated to each asset for a single PI using a Rolling 30-Day Window.
3
+ */
4
+ class PIDailyAssetAUM {
2
5
  static getMetadata() {
3
6
  return {
4
7
  type: 'standard',
5
8
  category: 'analytics',
6
9
  userType: 'POPULAR_INVESTOR',
7
10
 
8
- // 1. Load TODAY'S raw data (Cheap)
11
+ // 1. Core Data
9
12
  rootDataDependencies: ['portfolio', 'rankings'],
10
13
  mandatoryRoots: ['portfolio', 'rankings'],
11
14
 
12
- // 2. Load MY OWN history (Last 29 days of results)
13
- // This reads small result docs, not heavy portfolios
15
+ // 2. History: Load MY OWN results from the last 29 days
16
+ // CRITICAL: Name must match the File Name (PIDailyAssetAUM)
14
17
  dependencySeries: {
15
- 'PIDailyRollingAUM': 29
16
- }
18
+ 'PIDailyAssetAUM': 29
19
+ },
20
+
21
+ // 3. Bootstrap Flag: Allows running even if history is empty (First Run)
22
+ canHaveMissingSeries: true
17
23
  };
18
24
  }
19
25
 
20
26
  static getDependencies() { return []; }
21
27
 
22
- //
23
-
24
28
  async process(context) {
25
29
  const { extract, RankingsExtractor } = context.math;
26
30
  const userId = context.user.id;
@@ -47,55 +51,52 @@ class PIDailyRollingAUM {
47
51
  }
48
52
 
49
53
  // --- STEP B: Load History (T-1 to T-29) ---
50
- // Context: context.seriesData.results['PIDailyRollingAUM'][date][userId]
51
- const historyMap = context.globalData.series?.results?.['PIDailyRollingAUM'] || {};
54
+ // Access using the FILE NAME key
55
+ const historyMap = context.globalData.series?.results?.['PIDailyAssetAUM'] || {};
52
56
 
53
- // Collect all daily maps (History + Today)
54
57
  const rollingWindow = [];
55
58
 
56
- // 1. Add History
57
- Object.values(historyMap).forEach(dayResult => {
58
- // Depending on how you stored it, extract the 'dailyMap'
59
- // In this logic, we store { dailyMap: {...}, averageMap: {...} }
60
- const userResult = dayResult[userId];
61
- if (userResult && userResult.dailyMap) {
62
- rollingWindow.push(userResult.dailyMap);
63
- }
64
- });
59
+ if (historyMap) {
60
+ Object.values(historyMap).forEach(dayResult => {
61
+ const userResult = dayResult[userId];
62
+ if (userResult && userResult.dailyMap) {
63
+ rollingWindow.push(userResult.dailyMap);
64
+ }
65
+ });
66
+ }
65
67
 
66
- // 2. Add Today
68
+ // Always add Today
67
69
  rollingWindow.push(todayAllocation);
68
70
 
69
71
  // --- STEP C: Compute Rolling Average ---
70
- const summedStats = {}; // { AAPL: { total: 5000, count: 5 } }
72
+ const summedStats = {};
71
73
 
72
74
  rollingWindow.forEach(dayAllocations => {
73
75
  Object.entries(dayAllocations).forEach(([ticker, val]) => {
74
76
  if (!summedStats[ticker]) summedStats[ticker] = { total: 0, count: 0 };
75
77
  summedStats[ticker].total += val;
76
- summedStats[ticker].count += 1; // It existed on this day
78
+ summedStats[ticker].count += 1;
77
79
  });
78
80
  });
79
81
 
80
82
  const rollingAverage = {};
81
- const validDays = rollingWindow.length; // Denominator is number of valid days captured
83
+ const validDays = rollingWindow.length;
82
84
 
83
- Object.entries(summedStats).forEach(([ticker, stat]) => {
84
- // Average = Total Value / Number of Days in the Window
85
- // Note: If you want "Average Allocation when held", divide by stat.count
86
- // If you want "Average Portfolio Weight over 30 days", divide by validDays
87
- rollingAverage[ticker] = stat.total / validDays;
88
- });
85
+ if (validDays > 0) {
86
+ Object.entries(summedStats).forEach(([ticker, stat]) => {
87
+ rollingAverage[ticker] = stat.total / validDays;
88
+ });
89
+ }
89
90
 
90
- // --- STEP D: Save Hybrid Result ---
91
- // We save Today's values (for future history) AND the pre-calculated Average (for Meta)
91
+ // --- STEP D: Output Results ---
92
+ // StandardExecutor reads 'this.results', it does NOT use the return value.
92
93
  this.results = {
93
94
  [userId]: {
94
- dailyMap: todayAllocation, // Saved for tomorrow's history lookup
95
- averageMap: rollingAverage // Saved for today's Meta aggregator
95
+ dailyMap: todayAllocation,
96
+ averageMap: rollingAverage
96
97
  }
97
98
  };
98
99
  }
100
+ }
99
101
 
100
- async getResult() { return this.results; }
101
- }
102
+ module.exports = PIDailyAssetAUM;
@@ -1,19 +1,25 @@
1
1
  /**
2
- * @fileoverview Recommends PIs to Signed-In Users based on views, holdings, and sector alignment.
2
+ * @fileoverview Recommends PIs to Signed-In Users using concrete ID matching.
3
+ * MATCHING STRATEGY:
4
+ * 1. Exclusion: Filter PIs the user already copies (ParentCID).
5
+ * 2. Interest: Page Views (30D History) using 'viewsByUser' schema.
6
+ * 3. Asset Class: Match User's holdings (InstrumentTypeID) vs PI's TopTradedAssetClassId.
7
+ * 4. Instrument: Match User's holdings (InstrumentID) vs PI's TopTradedInstrumentId.
3
8
  */
4
9
  class RecommendedPopularInvestors {
5
10
  static getMetadata() {
6
11
  return {
7
12
  type: 'standard',
8
13
  category: 'recommendations',
9
- userType: 'SIGNED_IN_USER', // Targeted audience
14
+ userType: 'SIGNED_IN_USER',
10
15
 
11
- // We need Portfolio (for mirrors/sectors) and Rankings (for PI metadata)
16
+ // We need User Portfolio (Deep) and Global Rankings (Summary)
12
17
  rootDataDependencies: ['portfolio', 'rankings'],
13
18
  mandatoryRoots: ['portfolio', 'rankings'],
14
- canHaveMissingRoots: true, // Allow running even if pageViews are incomplete
19
+ canHaveMissingRoots: true, // Allow running even if pageViews/ratings missing
15
20
 
16
21
  // 30-Day History of Page Views for Interest Graph
22
+ // System loads the global daily docs: { [PiCID]: { viewsByUser: { [UserCID]: { viewCount: N } } } }
17
23
  rootDataSeries: {
18
24
  pageViews: 30
19
25
  }
@@ -26,7 +32,7 @@ class RecommendedPopularInvestors {
26
32
  return {
27
33
  type: 'object',
28
34
  patternProperties: {
29
- '^[0-9]+$': { // Keyed by User ID
35
+ '^[0-9]+$': {
30
36
  type: 'array',
31
37
  items: {
32
38
  type: 'object',
@@ -43,90 +49,124 @@ class RecommendedPopularInvestors {
43
49
  }
44
50
 
45
51
  async process(context) {
46
- const { extract, RankingsExtractor, CopyTradingExtractor, PageViewsExtractor } = context.math;
52
+ const { RankingsExtractor, CopyTradingExtractor, PageViewsExtractor, DataExtractor } = context.math;
47
53
  const userPortfolio = context.user.portfolio.today;
48
54
  const allRankings = context.globalData.rankings || [];
49
55
  const pageViewSeries = context.globalData.series.root.pageViews || {};
50
56
 
51
- // 1. Identify Already Copied PIs (Exclusion List)
57
+ // --- STEP 1: USER PROFILE EXTRACTION ---
58
+
59
+ // A. Who do I copy? (Exclusion List)
60
+ // Schema: AggregatedMirrors -> ParentCID (e.g., 5125148)
52
61
  const mirrors = CopyTradingExtractor.getAggregatedMirrors(userPortfolio);
53
62
  const copiedCids = new Set(mirrors.map(m => String(CopyTradingExtractor.getParentCID(m))));
54
63
  copiedCids.add(String(context.user.id)); // Exclude self
55
64
 
56
- // 2. Build Interest Score from Page Views (30 Day Lookback)
57
- // We traverse the global map: Series -> Date -> PI -> ViewsByUser -> ThisUser
58
- const piInterestScore = new Map(); // CID -> Score
59
-
60
- Object.values(pageViewSeries).forEach(dailyGlobalData => {
61
- // Optimization: We must iterate PIs to find our user
62
- // In a real high-scale scenario, we might want an inverted index,
63
- // but for <5000 PIs this is acceptable O(N).
64
- const allPiCids = PageViewsExtractor.getAllPIs(dailyGlobalData);
65
-
66
- for (const piCid of allPiCids) {
67
- if (copiedCids.has(piCid)) continue;
68
-
69
- // Check if *this* user viewed *this* PI
70
- const userViews = PageViewsExtractor.getUserViewCount(dailyGlobalData, piCid, context.user.id);
71
- if (userViews > 0) {
72
- const current = piInterestScore.get(piCid) || 0;
73
- piInterestScore.set(piCid, current + (userViews * 2)); // 2 points per view
74
- }
75
- }
65
+ // B. What Asset Classes do I hold?
66
+ // Schema: AggregatedPositionsByInstrumentTypeID -> InstrumentTypeID (e.g., 5=Crypto, 4=Stocks)
67
+ const userAssetClasses = new Set();
68
+ if (userPortfolio && Array.isArray(userPortfolio.AggregatedPositionsByInstrumentTypeID)) {
69
+ userPortfolio.AggregatedPositionsByInstrumentTypeID.forEach(grp => {
70
+ if (grp.InstrumentTypeID) userAssetClasses.add(Number(grp.InstrumentTypeID));
71
+ });
72
+ }
73
+
74
+ // C. What Instruments do I hold?
75
+ // Schema: AggregatedPositions -> InstrumentID (e.g., 1353)
76
+ const userInstruments = new Set();
77
+ const positions = DataExtractor.getPositions(userPortfolio, context.user.type);
78
+ positions.forEach(pos => {
79
+ const id = DataExtractor.getInstrumentId(pos);
80
+ if (id) userInstruments.add(Number(id));
76
81
  });
77
82
 
78
- // 3. Analyze User Sectors
79
- const userSectors = new Set(extract.getCurrentSectors(userPortfolio, context.mappings, context.user.type));
83
+ // --- STEP 2: INTEREST GRAPH (Page Views) ---
84
+ // Iterate through 30 days of global page view maps
85
+ const piInterestScore = new Map();
86
+ if (pageViewSeries) {
87
+ Object.values(pageViewSeries).forEach(dailyGlobalData => {
88
+ // dailyGlobalData matches your schema: { "31075566": { viewsByUser: { ... } } }
89
+
90
+ // Extractor handles filtering 'lastUpdated' and iterating PIs
91
+ const allPiCids = PageViewsExtractor.getAllPIs(dailyGlobalData);
92
+
93
+ for (const piCid of allPiCids) {
94
+ if (copiedCids.has(piCid)) continue;
95
+
96
+ // Extractor looks inside 'viewsByUser' -> [UserCID] -> 'viewCount'
97
+ const userViews = PageViewsExtractor.getUserViewCount(dailyGlobalData, piCid, context.user.id);
98
+
99
+ if (userViews > 0) {
100
+ const current = piInterestScore.get(piCid) || 0;
101
+ // Score logic: 2 points per view
102
+ piInterestScore.set(piCid, current + (userViews * 2));
103
+ }
104
+ }
105
+ });
106
+ }
80
107
 
81
- // 4. Score Candidates
108
+ // --- STEP 3: MATCHING LOGIC ---
82
109
  const recommendations = [];
83
110
 
84
111
  for (const piEntry of allRankings) {
85
112
  const piCid = String(piEntry.CustomerId || piEntry.cid);
113
+
114
+ // 1. Exclusion
86
115
  if (copiedCids.has(piCid)) continue;
87
116
 
88
117
  let score = 0;
89
118
  const reasons = [];
90
119
 
91
- // A. Page View Interest
120
+ // 2. Interest (Page Views)
92
121
  const viewScore = piInterestScore.get(piCid) || 0;
93
122
  if (viewScore > 0) {
94
- score += Math.min(20, viewScore); // Cap at 20 points
123
+ score += Math.min(20, viewScore);
95
124
  reasons.push('Recently Viewed');
96
125
  }
97
126
 
98
- // B. Sector Match
99
- const piTags = RankingsExtractor.getTags(piEntry);
100
- const hasSectorMatch = piTags.some(tag => userSectors.has(tag));
101
- if (hasSectorMatch) {
127
+ // 3. Asset Class Match (Hard ID Match)
128
+ // Schema: Rankings -> TopTradedAssetClassId (e.g., 5)
129
+ const piAssetClass = piEntry.TopTradedAssetClassId;
130
+ if (piAssetClass && userAssetClasses.has(piAssetClass)) {
102
131
  score += 15;
103
- reasons.push('Matches Your Sectors');
132
+ reasons.push('Trades Your Asset Classes');
133
+ }
134
+
135
+ // 4. Top Instrument Match (Hard ID Match)
136
+ // Schema: Rankings -> TopTradedInstrumentId (e.g., 1155)
137
+ const piTopInst = piEntry.TopTradedInstrumentId;
138
+ if (piTopInst && userInstruments.has(piTopInst)) {
139
+ score += 10;
140
+ reasons.push('Trades Your Top Assets');
104
141
  }
105
142
 
106
- // C. Base Quality (Risk/AUM)
143
+ // 5. Risk Filter (Baseline Quality)
144
+ // Schema: Rankings -> RiskScore
107
145
  const risk = RankingsExtractor.getRiskScore(piEntry);
108
- if (risk <= 6) score += 5; // Prefer stable PIs
109
-
110
- // Only recommend if there is some signal
146
+ if (risk <= 6) {
147
+ score += 5;
148
+ reasons.push('Stable Risk Score');
149
+ }
150
+
151
+ // Push if there is a signal
111
152
  if (score > 0) {
112
153
  recommendations.push({
113
154
  cid: piCid,
114
155
  username: piEntry.UserName || "Unknown",
115
156
  matchScore: score,
116
- reason: reasons[0]
157
+ // Ensure a default reason exists
158
+ reason: reasons[0] || 'Recommended Strategy'
117
159
  });
118
160
  }
119
161
  }
120
162
 
121
- // 5. Sort and Limit
163
+ // --- STEP 4: SORT & LIMIT ---
122
164
  recommendations.sort((a, b) => b.matchScore - a.matchScore);
123
165
 
124
166
  this.results = {
125
167
  [context.user.id]: recommendations.slice(0, 5)
126
168
  };
127
169
  }
128
-
129
- async getResult() { return this.results; }
130
170
  }
131
171
 
132
172
  module.exports = RecommendedPopularInvestors;
@@ -54,9 +54,8 @@ class RiskLeaderboard {
54
54
  }));
55
55
 
56
56
  this.results = rankedResult;
57
- }
58
-
59
- async getResult() {
57
+
58
+ // CRITICAL FIX: MetaExecutor requires the data to be returned
60
59
  return this.results;
61
60
  }
62
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiden-shared-calculations-unified",
3
- "version": "1.0.148",
3
+ "version": "1.0.149",
4
4
  "description": "Shared calculation modules for the BullTrackers Computation System.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -1,70 +0,0 @@
1
- /**
2
- * @fileoverview Aggregate AUM per Asset Across All Users.
3
- * Returns the AUM invested into each asset total across all users.
4
- */
5
-
6
- class AggregateAssetAUM {
7
- constructor() {
8
- this.results = {};
9
- }
10
-
11
- static getMetadata() {
12
- return {
13
- type: 'meta',
14
- category: 'popular_investor',
15
- userType: 'POPULAR_INVESTOR',
16
- // [FIX] Enforce strict availability check.
17
- // Prevents execution if PI data is missing, even if upstream calc exists.
18
- rootDataDependencies: ['portfolio']
19
- };
20
- }
21
-
22
- static getDependencies() { return ['UserAUMPerAsset']; }
23
-
24
- static getSchema() {
25
- return {
26
- type: 'object',
27
- additionalProperties: { type: 'number' },
28
- description: 'Map of instrumentId -> total AUM across all users'
29
- };
30
- }
31
-
32
- process(context) {
33
- // Input: Map of { userId: { instrumentId: aum } }
34
- const aumPerAssetData = context.computed['UserAUMPerAsset'];
35
-
36
- if (!aumPerAssetData) {
37
- return {};
38
- }
39
-
40
- // Aggregate AUM per instrument across all users
41
- const instrumentAUM = new Map();
42
-
43
- for (const [userId, userAssets] of Object.entries(aumPerAssetData)) {
44
- if (!userAssets || typeof userAssets !== 'object') continue;
45
-
46
- for (const [instrumentIdStr, aum] of Object.entries(userAssets)) {
47
- const instrumentId = Number(instrumentIdStr);
48
- // Validate AUM is a number and valid instrument ID
49
- if (isNaN(instrumentId) || typeof aum !== 'number' || aum <= 0) continue;
50
-
51
- const current = instrumentAUM.get(instrumentId) || 0;
52
- instrumentAUM.set(instrumentId, current + aum);
53
- }
54
- }
55
-
56
- // Convert Map to output Object
57
- const result = {};
58
- for (const [instrumentId, totalAUM] of instrumentAUM.entries()) {
59
- result[instrumentId] = Number(totalAUM.toFixed(2));
60
- }
61
-
62
- return result;
63
- }
64
-
65
- async getResult() {
66
- return this.results;
67
- }
68
- }
69
-
70
- module.exports = AggregateAssetAUM;
@@ -1,72 +0,0 @@
1
- /**
2
- * @fileoverview Aggregate Daily AUM Across All Users.
3
- * Returns the overall AUM values each day total across all users.
4
- */
5
-
6
- class AggregateDailyAUM {
7
- constructor() {
8
- this.results = {};
9
- }
10
-
11
- static getMetadata() {
12
- return {
13
- type: 'meta',
14
- category: 'popular_investor',
15
- userType: 'POPULAR_INVESTOR'
16
- };
17
- }
18
-
19
- static getDependencies() { return ['UserAUM30Day']; }
20
-
21
- static getSchema() {
22
- return {
23
- type: 'object',
24
- additionalProperties: { type: 'number' },
25
- description: 'Map of date -> total AUM across all users'
26
- };
27
- }
28
-
29
- process(context) {
30
- const aumData = context.computed['UserAUM30Day'];
31
-
32
- // [FIX] Defensive check
33
- if (!aumData) {
34
- return {};
35
- }
36
-
37
- // Aggregate AUM by date
38
- const dateMap = {};
39
-
40
- for (const [userId, userHistory] of Object.entries(aumData)) {
41
- // [FIX] Validate userHistory exists
42
- if (!userHistory) continue;
43
-
44
- // [FIX] "30Day" data is an Array of daily entries.
45
- // We must iterate the array. We also handle the edge case where it might be a single object.
46
- const entries = Array.isArray(userHistory) ? userHistory : [userHistory];
47
-
48
- for (const entry of entries) {
49
- if (entry && typeof entry === 'object' && entry.date && typeof entry.aum === 'number') {
50
- const date = entry.date;
51
- // Sum the AUM for this specific date
52
- dateMap[date] = (dateMap[date] || 0) + entry.aum;
53
- }
54
- }
55
- }
56
-
57
- // Round values to 2 decimals
58
- const rounded = {};
59
- for (const [date, total] of Object.entries(dateMap)) {
60
- rounded[date] = Number(total.toFixed(2));
61
- }
62
-
63
- // [FIX] Must RETURN the result for MetaExecutor
64
- return rounded;
65
- }
66
-
67
- async getResult() {
68
- return this.results;
69
- }
70
- }
71
-
72
- module.exports = AggregateDailyAUM;
@@ -1,67 +0,0 @@
1
- /**
2
- * @fileoverview AUM Value Over Last 30 Days for Users.
3
- * Returns the AUM value for the current day.
4
- * (Historical aggregation handled by Database/API or 'isHistorical' mechanisms in Meta scripts).
5
- */
6
-
7
- class UserAUM30Day {
8
- constructor() {
9
- this.results = {};
10
- }
11
-
12
- static getMetadata() {
13
- return {
14
- type: 'standard',
15
- category: 'popular_investor',
16
- rootDataDependencies: ['portfolio', 'rankings'],
17
- userType: 'POPULAR_INVESTOR'
18
- };
19
- }
20
-
21
- static getDependencies() { return []; }
22
-
23
- static getSchema() {
24
- return {
25
- type: 'object',
26
- properties: {
27
- date: { type: 'string', format: 'date' },
28
- aum: { type: 'number' }
29
- }
30
- };
31
- }
32
-
33
- process(context) {
34
- const { RankingsExtractor, PopularInvestorExtractor } = context.math;
35
- const userId = context.user.id;
36
- const portfolio = context.user.portfolio.today;
37
- const rankEntry = context.user.rankEntry;
38
- const dateStr = context.date.today;
39
-
40
- let aum = 0;
41
-
42
- // 1. Priority: Official Rankings Data
43
- if (rankEntry) {
44
- aum = RankingsExtractor.getAUMValue(rankEntry) || 0;
45
- }
46
-
47
- // 2. Fallback: Portfolio Snapshot Data
48
- if ((!aum || aum === 0) && portfolio) {
49
- if (typeof portfolio.AUMValue === 'number') {
50
- aum = portfolio.AUMValue;
51
- } else {
52
- aum = PopularInvestorExtractor.getEquity(portfolio);
53
- }
54
- }
55
-
56
- this.results[userId] = {
57
- date: dateStr,
58
- aum: Number(aum.toFixed(2))
59
- };
60
- }
61
-
62
- async getResult() {
63
- return this.results;
64
- }
65
- }
66
-
67
- module.exports = UserAUM30Day;
@@ -1,67 +0,0 @@
1
- /**
2
- * @fileoverview AUM per Asset for Users.
3
- * Returns the AUM invested into each asset per user.
4
- */
5
-
6
- class UserAUMPerAsset {
7
- constructor() {
8
- this.results = {};
9
- }
10
-
11
- static getMetadata() {
12
- return {
13
- type: 'standard',
14
- category: 'popular_investor',
15
- rootDataDependencies: ['portfolio'],
16
- userType: 'POPULAR_INVESTOR'
17
- };
18
- }
19
-
20
- static getDependencies() { return []; }
21
-
22
- static getSchema() {
23
- return {
24
- type: 'object',
25
- additionalProperties: { type: 'number' },
26
- description: 'Map of instrumentId -> AUM value invested'
27
- };
28
- }
29
-
30
- process(context) {
31
- const { DataExtractor } = context.math;
32
- const userId = context.user.id;
33
- const portfolio = context.user.portfolio.today;
34
-
35
- if (!portfolio) {
36
- this.results[userId] = {};
37
- return;
38
- }
39
-
40
- // [FIX] Inline aggregation logic
41
- const positions = DataExtractor.getPositions(portfolio, 'POPULAR_INVESTOR');
42
- const aumMap = {};
43
-
44
- for (const pos of positions) {
45
- const instId = DataExtractor.getInstrumentId(pos);
46
- const value = DataExtractor.getPositionValue(pos); // Value in USD
47
-
48
- if (instId && value > 0) {
49
- // Ensure key is numeric or string consistent with consumption
50
- aumMap[instId] = (aumMap[instId] || 0) + value;
51
- }
52
- }
53
-
54
- // Round values
55
- for (const key in aumMap) {
56
- aumMap[key] = Number(aumMap[key].toFixed(2));
57
- }
58
-
59
- this.results[userId] = aumMap;
60
- }
61
-
62
- async getResult() {
63
- return this.results;
64
- }
65
- }
66
-
67
- module.exports = UserAUMPerAsset;