bulltrackers-module 1.0.766 → 1.0.769

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 (76) hide show
  1. package/functions/computation-system-v2/UserPortfolioMetrics.js +50 -0
  2. package/functions/computation-system-v2/computations/BehavioralAnomaly.js +559 -227
  3. package/functions/computation-system-v2/computations/GlobalAumPerAsset30D.js +103 -0
  4. package/functions/computation-system-v2/computations/NewSectorExposure.js +82 -35
  5. package/functions/computation-system-v2/computations/NewSocialPost.js +52 -24
  6. package/functions/computation-system-v2/computations/PIDailyAssetAUM.js +134 -0
  7. package/functions/computation-system-v2/computations/PiFeatureVectors.js +227 -0
  8. package/functions/computation-system-v2/computations/PiRecommender.js +359 -0
  9. package/functions/computation-system-v2/computations/PopularInvestorProfileMetrics.js +354 -641
  10. package/functions/computation-system-v2/computations/SignedInUserList.js +51 -0
  11. package/functions/computation-system-v2/computations/SignedInUserMirrorHistory.js +138 -0
  12. package/functions/computation-system-v2/computations/SignedInUserPIProfileMetrics.js +106 -0
  13. package/functions/computation-system-v2/computations/SignedInUserProfileMetrics.js +324 -0
  14. package/functions/computation-system-v2/config/bulltrackers.config.js +40 -126
  15. package/functions/computation-system-v2/core-api.js +17 -9
  16. package/functions/computation-system-v2/data_schema_reference.MD +108 -0
  17. package/functions/computation-system-v2/devtools/builder/builder.js +362 -0
  18. package/functions/computation-system-v2/devtools/builder/examples/user-metrics.yaml +26 -0
  19. package/functions/computation-system-v2/devtools/index.js +36 -0
  20. package/functions/computation-system-v2/devtools/shared/MockDataFactory.js +235 -0
  21. package/functions/computation-system-v2/devtools/shared/SchemaTemplates.js +475 -0
  22. package/functions/computation-system-v2/devtools/shared/SystemIntrospector.js +517 -0
  23. package/functions/computation-system-v2/devtools/shared/index.js +16 -0
  24. package/functions/computation-system-v2/devtools/simulation/DAGAnalyzer.js +243 -0
  25. package/functions/computation-system-v2/devtools/simulation/MockDataFetcher.js +306 -0
  26. package/functions/computation-system-v2/devtools/simulation/MockStorageManager.js +336 -0
  27. package/functions/computation-system-v2/devtools/simulation/SimulationEngine.js +525 -0
  28. package/functions/computation-system-v2/devtools/simulation/SimulationServer.js +581 -0
  29. package/functions/computation-system-v2/devtools/simulation/index.js +17 -0
  30. package/functions/computation-system-v2/devtools/simulation/simulate.js +324 -0
  31. package/functions/computation-system-v2/devtools/vscode-computation/package.json +90 -0
  32. package/functions/computation-system-v2/devtools/vscode-computation/snippets/computation.json +128 -0
  33. package/functions/computation-system-v2/devtools/vscode-computation/src/extension.ts +401 -0
  34. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/codeActions.ts +152 -0
  35. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/completions.ts +207 -0
  36. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/diagnostics.ts +205 -0
  37. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/hover.ts +205 -0
  38. package/functions/computation-system-v2/devtools/vscode-computation/tsconfig.json +22 -0
  39. package/functions/computation-system-v2/docs/HowToCreateComputations.MD +602 -0
  40. package/functions/computation-system-v2/framework/core/Manifest.js +9 -16
  41. package/functions/computation-system-v2/framework/core/RunAnalyzer.js +2 -1
  42. package/functions/computation-system-v2/framework/data/DataFetcher.js +330 -126
  43. package/functions/computation-system-v2/framework/data/MaterializedViewManager.js +84 -0
  44. package/functions/computation-system-v2/framework/data/QueryBuilder.js +38 -38
  45. package/functions/computation-system-v2/framework/execution/Orchestrator.js +226 -153
  46. package/functions/computation-system-v2/framework/scheduling/ScheduleValidator.js +17 -19
  47. package/functions/computation-system-v2/framework/storage/StateRepository.js +32 -2
  48. package/functions/computation-system-v2/framework/storage/StorageManager.js +111 -83
  49. package/functions/computation-system-v2/framework/testing/ComputationTester.js +161 -66
  50. package/functions/computation-system-v2/handlers/dispatcher.js +57 -29
  51. package/functions/computation-system-v2/legacy/PiAssetRecommender.js.old +115 -0
  52. package/functions/computation-system-v2/legacy/PiSimilarityMatrix.js +104 -0
  53. package/functions/computation-system-v2/legacy/PiSimilarityVector.js +71 -0
  54. package/functions/computation-system-v2/scripts/debug_aggregation.js +25 -0
  55. package/functions/computation-system-v2/scripts/test-computation-dag.js +109 -0
  56. package/functions/computation-system-v2/scripts/test-invalidation-scenarios.js +234 -0
  57. package/functions/task-engine/helpers/data_storage_helpers.js +6 -6
  58. package/package.json +1 -1
  59. package/functions/computation-system-v2/computations/PopularInvestorRiskAssessment.js +0 -176
  60. package/functions/computation-system-v2/computations/PopularInvestorRiskMetrics.js +0 -294
  61. package/functions/computation-system-v2/computations/UserPortfolioSummary.js +0 -172
  62. package/functions/computation-system-v2/scripts/migrate-sectors.js +0 -73
  63. package/functions/computation-system-v2/test/analyze-results.js +0 -238
  64. package/functions/computation-system-v2/test/other/test-dependency-cascade.js +0 -150
  65. package/functions/computation-system-v2/test/other/test-dispatcher.js +0 -317
  66. package/functions/computation-system-v2/test/other/test-framework.js +0 -500
  67. package/functions/computation-system-v2/test/other/test-real-execution.js +0 -166
  68. package/functions/computation-system-v2/test/other/test-real-integration.js +0 -194
  69. package/functions/computation-system-v2/test/other/test-refactor-e2e.js +0 -131
  70. package/functions/computation-system-v2/test/other/test-results.json +0 -31
  71. package/functions/computation-system-v2/test/other/test-risk-metrics-computation.js +0 -329
  72. package/functions/computation-system-v2/test/other/test-scheduler.js +0 -204
  73. package/functions/computation-system-v2/test/other/test-storage.js +0 -449
  74. package/functions/computation-system-v2/test/run-pipeline-test.js +0 -554
  75. package/functions/computation-system-v2/test/test-full-pipeline.js +0 -227
  76. package/functions/computation-system-v2/test/test-worker-pool.js +0 -266
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @fileoverview Similarity Vector for Popular Investors.
3
+ * Generates a normalized asset allocation vector.
4
+ */
5
+
6
+ class PiSimilarityVector {
7
+ constructor() {
8
+ this.results = {};
9
+ }
10
+
11
+ static getMetadata() {
12
+ return {
13
+ type: 'standard', // MUST be '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
+ patternProperties: {
26
+ "^[0-9]+$": { type: 'number' } // InstrumentID -> Weight
27
+ },
28
+ description: 'Normalized Asset Allocation Vector'
29
+ };
30
+ }
31
+
32
+ process(context) {
33
+ const { DataExtractor } = context.math;
34
+ const userId = context.user.id;
35
+ const portfolio = context.user.portfolio.today;
36
+
37
+ if (!portfolio) {
38
+ this.results[userId] = {};
39
+ return;
40
+ }
41
+
42
+ const positions = DataExtractor.getPositions(portfolio, 'POPULAR_INVESTOR');
43
+ const vector = {};
44
+ let totalValue = 0;
45
+
46
+ for (const pos of positions) {
47
+ const id = DataExtractor.getInstrumentId(pos);
48
+ const weight = DataExtractor.getPositionWeight(pos);
49
+
50
+ if (id && weight > 0) {
51
+ vector[id] = (vector[id] || 0) + weight;
52
+ totalValue += weight;
53
+ }
54
+ }
55
+
56
+ // Normalize
57
+ if (totalValue > 0) {
58
+ for (const key in vector) {
59
+ vector[key] = Number((vector[key] / totalValue).toFixed(6));
60
+ }
61
+ }
62
+
63
+ this.results[userId] = vector;
64
+ }
65
+
66
+ async getResult() {
67
+ return this.results;
68
+ }
69
+ }
70
+
71
+ module.exports = PiSimilarityVector;
@@ -0,0 +1,25 @@
1
+ const { BigQuery } = require('@google-cloud/bigquery');
2
+ const config = require('../config/bulltrackers.config');
3
+
4
+ async function checkAggregation() {
5
+ const bq = new BigQuery({ projectId: config.bigquery.projectId });
6
+ const date = '2026-01-30';
7
+
8
+ console.log(`Checking Aggregation on ${date}`);
9
+
10
+ const query = `
11
+ SELECT user_type, COUNT(*) as count
12
+ FROM \`${config.bigquery.projectId}.${config.bigquery.dataset}.portfolio_snapshots\`
13
+ WHERE date = @date
14
+ GROUP BY 1
15
+ `;
16
+
17
+ const [rows] = await bq.query({
18
+ query,
19
+ params: { date }
20
+ });
21
+
22
+ console.log('Result:', rows);
23
+ }
24
+
25
+ checkAggregation().catch(console.error);
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @fileoverview Test Computation DAG CLI
3
+ * Usage: node scripts/test-computation-dag.js <ComputationName> [Date]
4
+ * Example: node scripts/test-computation-dag.js UserPortfolioSummary
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { IntegrationTester } = require('../framework/testing/ComputationTester');
10
+ const config = require('../config/bulltrackers.config');
11
+
12
+ // Setup logging
13
+ const logger = {
14
+ log: (level, msg) => console.log(`[${new Date().toISOString()}] ${level}: ${msg}`),
15
+ error: (msg) => console.error(`[ERROR] ${msg}`)
16
+ };
17
+
18
+ async function main() {
19
+ const args = process.argv.slice(2);
20
+ if (args.length < 1) {
21
+ console.log('Usage: node scripts/test-computation-dag.js <ComputationName> [YYYY-MM-DD]');
22
+ process.exit(1);
23
+ }
24
+
25
+ const targetName = args[0];
26
+ let dateStr = args[1]; // Optional
27
+
28
+ try {
29
+ console.log('┌────────────────────────────────────────────────────────┐');
30
+ console.log('│ COMPUTATION INTEGRATION TEST │');
31
+ console.log('└────────────────────────────────────────────────────────┘');
32
+ console.log(`Target: ${targetName}`);
33
+ console.log(`Mode: DAG Integration (Production Read / Test Write)`);
34
+
35
+ const tester = new IntegrationTester(config, logger);
36
+
37
+ // 1. Setup & Resolve Chain
38
+ await tester.setupForComputation(targetName);
39
+
40
+ // 2. Find Date if not provided
41
+ if (!dateStr) {
42
+ console.log('\n🔍 Auto-detecting latest runnable date...');
43
+ dateStr = await tester.findLatestRunnableDate(targetName);
44
+ }
45
+
46
+ // 3. Execute
47
+ console.log(`\n🚀 Executing chain on ${dateStr}...`);
48
+ const output = await tester.run(targetName, dateStr);
49
+
50
+ // 4. Report
51
+ const { summary, result } = output;
52
+
53
+ console.log('\n┌────────────────────────────────────────────────────────┐');
54
+ console.log('│ TEST RESULTS │');
55
+ console.log('└────────────────────────────────────────────────────────┘');
56
+
57
+ if (summary.summary.errors > 0) {
58
+ console.log('❌ Execution FAILED');
59
+ summary.errors.forEach(e => console.log(` - ${e.name}: ${e.error}`));
60
+ } else {
61
+ console.log('✅ Execution SUCCESS');
62
+
63
+ // SQL computations: result is { _sqlOutput, outputTable, rowCount, rows }
64
+ if (result && result._sqlOutput) {
65
+ console.log(`\n📊 SQL output table: \`${config.bigquery.dataset}.${result.outputTable}\``);
66
+ console.log(` - Rows for ${dateStr}: ${result.rowCount}`);
67
+ if (result._error) {
68
+ console.log(` - ⚠️ Read error: ${result._error}`);
69
+ } else if (result.rowCount > 0) {
70
+ const sample = result.rows[0];
71
+ console.log(` - Sample row: ${JSON.stringify(sample).substring(0, 120)}...`);
72
+ const filename = `test-results-${targetName}-${dateStr}.json`;
73
+ const outputPath = path.join(__dirname, '..', filename);
74
+ fs.writeFileSync(outputPath, JSON.stringify(result.rows, null, 2));
75
+ console.log(`\n💾 Full rows saved to: ${outputPath}`);
76
+ } else {
77
+ console.log(' - No rows for this date. Check source table has data: portfolio_snapshots for this date.');
78
+ }
79
+ console.log(`\nQuery in BQ: SELECT * FROM \`${config.bigquery.projectId}.${config.bigquery.dataset}.${result.outputTable}\` WHERE date = '${dateStr}'`);
80
+ } else if (result) {
81
+ // Non-SQL: result from computation_results_test
82
+ const resultKeys = Object.keys(result);
83
+ const sampleKey = resultKeys[0];
84
+ const sampleData = resultKeys.length > 0 ? result[sampleKey] : null;
85
+
86
+ console.log(`\n📊 Result Summary for ${targetName}:`);
87
+ console.log(` - Count: ${resultKeys.length} entities/records`);
88
+ console.log(` - Sample ID: ${sampleKey || 'N/A'}`);
89
+ if (sampleData) {
90
+ console.log(` - Sample Data Preview: ${JSON.stringify(sampleData).substring(0, 100)}...`);
91
+ }
92
+ const filename = `test-results-${targetName}-${dateStr}.json`;
93
+ const outputPath = path.join(__dirname, '..', filename);
94
+ fs.writeFileSync(outputPath, JSON.stringify(result, null, 2));
95
+ console.log(`\n💾 Full results saved to: ${outputPath}`);
96
+ console.log(`Query results in BQ: SELECT * FROM \`${config.bigquery.dataset}.computation_results_test\` WHERE computation_name = '${targetName}' AND date = '${dateStr}'`);
97
+ } else {
98
+ console.log('⚠️ No results produced (Computation returned empty set).');
99
+ }
100
+ }
101
+
102
+ } catch (e) {
103
+ console.error('\n❌ FATAL ERROR:');
104
+ console.error(e);
105
+ process.exit(1);
106
+ }
107
+ }
108
+
109
+ main();
@@ -0,0 +1,234 @@
1
+ /**
2
+ * @fileoverview Real-World Invalidation Simulator
3
+ * Dynamically loads ALL computation AND rule code to verify:
4
+ * 1. Code Hash Generation (using real files).
5
+ * 2. Invalidation logic (Simulated DB vs Real Disk Hash).
6
+ * 3. Dependency chains (Dynamic discovery of chains).
7
+ * * Usage: node scripts/test-real-scenarios.js
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const assert = require('assert');
13
+
14
+ // Framework Components
15
+ const { RunAnalyzer } = require('../framework/core/RunAnalyzer');
16
+ const { Orchestrator } = require('../framework/execution/Orchestrator');
17
+ const { ManifestBuilder } = require('../framework/core/Manifest');
18
+
19
+ // -----------------------------------------------------------------------------
20
+ // 1. MOCKS (Virtual Environment)
21
+ // -----------------------------------------------------------------------------
22
+
23
+ class MockDataFetcher {
24
+ async checkAvailability() { return { canRun: true, missing: [] }; }
25
+ }
26
+
27
+ class MockStateRepository {
28
+ constructor() {
29
+ this.statusMap = new Map();
30
+ this.results = new Map();
31
+ }
32
+
33
+ setDailyStatus(date, compName, status) {
34
+ if (!this.statusMap.has(date)) this.statusMap.set(date, new Map());
35
+ this.statusMap.get(date).set(compName.toLowerCase(), status);
36
+ }
37
+
38
+ async getDailyStatus(date) {
39
+ return this.statusMap.get(date) || new Map();
40
+ }
41
+
42
+ // Simulate finding history for backfills
43
+ async getRunDates(compName) { return ['2024-01-01', '2024-01-02']; }
44
+
45
+ // Mock Result fetching
46
+ async getResult(date, compName) { return this.results.get(`${date}:${compName}`); }
47
+ }
48
+
49
+ class TestOrchestrator extends Orchestrator {
50
+ constructor(config, stateRepo) {
51
+ super(config);
52
+ this.stateRepository = stateRepo;
53
+ this.scheduledTasks = [];
54
+ }
55
+ async _scheduleCloudTask(name, date, source, delay) {
56
+ this.scheduledTasks.push({ name, date, source });
57
+ }
58
+ _log(level, msg) { /* console.log(`[${level}] ${msg}`); */ }
59
+ }
60
+
61
+ // -----------------------------------------------------------------------------
62
+ // 2. HELPER: Dynamic Loader
63
+ // -----------------------------------------------------------------------------
64
+ function loadModulesFromDir(directory, label) {
65
+ let modules = [];
66
+ try {
67
+ if (fs.existsSync(directory)) {
68
+ const files = fs.readdirSync(directory).filter(f => f.endsWith('.js') && f !== 'index.js');
69
+ console.log(`INFO: Found ${files.length} ${label} files in ${directory}`);
70
+
71
+ modules = files.map(file => {
72
+ try {
73
+ return require(path.join(directory, file));
74
+ } catch (e) {
75
+ console.warn(`WARN: Failed to load ${file}: ${e.message}`);
76
+ return null;
77
+ }
78
+ }).filter(c => c !== null);
79
+ } else {
80
+ console.warn(`WARN: ${label} directory not found at ${directory}`);
81
+ }
82
+ } catch (e) {
83
+ console.error(`ERROR: Could not load ${label}: ${e.message}`);
84
+ }
85
+ return modules;
86
+ }
87
+
88
+ // -----------------------------------------------------------------------------
89
+ // 3. TEST SUITE
90
+ // -----------------------------------------------------------------------------
91
+
92
+ async function runRealWorldTests() {
93
+ console.log('┌────────────────────────────────────────────────────────┐');
94
+ console.log('│ REAL CODE SIMULATION SUITE │');
95
+ console.log('└────────────────────────────────────────────────────────┘');
96
+
97
+ // 1. LOAD ARTIFACTS
98
+ const computationsDir = path.join(__dirname, '../computations');
99
+ const rulesDir = path.join(__dirname, '../rules');
100
+
101
+ const computationClasses = loadModulesFromDir(computationsDir, 'computation');
102
+ const ruleModules = loadModulesFromDir(rulesDir, 'rule'); // <--- NEW: Load Rules
103
+
104
+ if (computationClasses.length === 0) {
105
+ console.error("❌ ABORT: No valid computations found to test.");
106
+ return;
107
+ }
108
+
109
+ // 2. BUILD REAL MANIFEST
110
+ const config = {
111
+ computations: computationClasses,
112
+ rules: ruleModules // <--- NEW: Pass Rules to Config
113
+ };
114
+
115
+ // Initialize ManifestBuilder (this internally initializes RulesRegistry too)
116
+ const builder = new ManifestBuilder(config, console);
117
+ const realManifest = builder.build(config.computations);
118
+
119
+ console.log(`INFO: Successfully built manifest with ${realManifest.length} entries.`);
120
+
121
+ // 3. RUN TESTS
122
+ await test_RealCodeInvalidation(realManifest);
123
+ await test_DynamicDependencyCascading(realManifest, config);
124
+ }
125
+
126
+ /**
127
+ * SCENARIO 1: Simulate a Deployment (Code Change)
128
+ */
129
+ async function test_RealCodeInvalidation(manifest) {
130
+ // Pick 'RiskScoreIncrease' if available, otherwise the first one
131
+ let target = manifest.find(c => c.name === 'riskscoreincrease');
132
+ if (!target) target = manifest[0];
133
+
134
+ console.log(`\n🧪 Test 1: Real Code Hash Mismatch (${target.originalName})`);
135
+
136
+ const analyzer = new RunAnalyzer(manifest, new MockDataFetcher());
137
+ const date = '2024-06-01';
138
+
139
+ // CONDITION: The DB has an old/different hash
140
+ const dailyStatus = new Map();
141
+ dailyStatus.set(target.name, {
142
+ hash: 'OLD_DEPLOYMENT_HASH_123',
143
+ resultHash: 'res_v1',
144
+ updatedAt: new Date().toISOString()
145
+ });
146
+
147
+ // ANALYZE
148
+ const result = await analyzer._evaluateEntry(target, date, true, dailyStatus, new Map());
149
+
150
+ // ASSERT
151
+ try {
152
+ assert.strictEqual(result.type, 'reRuns', 'Should trigger re-run');
153
+ assert.strictEqual(result.payload.reason, 'Code changed', 'Reason must be code change');
154
+
155
+ console.log(` [Current Disk Hash]: ${target.hash.substring(0, 16)}...`);
156
+ console.log(` [Simulated DB Hash]: OLD_DEPLOYMENT_HASH_123`);
157
+ console.log('✅ PASS: System correctly detected the code mismatch.');
158
+ } catch (e) {
159
+ console.error('❌ FAIL:', e.message);
160
+ console.log(' Decision:', result);
161
+ }
162
+ }
163
+
164
+ /**
165
+ * SCENARIO 2: Dynamic Dependency Propagation
166
+ */
167
+ async function test_DynamicDependencyCascading(manifest, config) {
168
+ console.log('\n🧪 Test 2: Dependency Propagation (Dynamic Chain Detection)');
169
+
170
+ // 1. Find a valid dependency chain (Parent -> Child)
171
+ let upstream = null;
172
+ let downstream = null;
173
+
174
+ for (const child of manifest) {
175
+ if (child.dependencies && child.dependencies.length > 0) {
176
+ // Find the parent object
177
+ const parentName = child.dependencies[0]; // normalized name usually
178
+ const parent = manifest.find(c => c.name === parentName); // match normalized names
179
+
180
+ if (parent) {
181
+ upstream = parent;
182
+ downstream = child;
183
+ break; // Found a pair!
184
+ }
185
+ }
186
+ }
187
+
188
+ // 2. Handle Case: No Chains Found
189
+ if (!upstream || !downstream) {
190
+ console.log('⚠️ SKIPPED: No valid dependency chains found in the loaded computations.');
191
+ console.log(' (This is NOT a failure. It simply means all loaded computations are independent)');
192
+ console.log('✅ PASS: Skipped due to lack of testable chains.');
193
+ return;
194
+ }
195
+
196
+ console.log(` Found chain: ${upstream.originalName} -> ${downstream.originalName}`);
197
+
198
+ // 3. Setup Orchestrator
199
+ const mockState = new MockStateRepository();
200
+ const orch = new TestOrchestrator({ ...config, bigquery: {}, tables: {} }, mockState);
201
+ orch.manifest = manifest;
202
+ orch._buildDependentsIndex();
203
+
204
+ const date = '2024-06-01';
205
+
206
+ // 4. Simulate Upstream Finishing
207
+ mockState.setDailyStatus(date, upstream.name, {
208
+ hash: upstream.hash,
209
+ resultHash: 'NEW_RESULT_HASH_XYZ',
210
+ updatedAt: new Date().toISOString()
211
+ });
212
+
213
+ // 5. Trigger Cascading Logic
214
+ await orch._scheduleDependents(upstream, date);
215
+
216
+ // 6. Assert
217
+ try {
218
+ const task = orch.scheduledTasks.find(t => t.name === downstream.originalName);
219
+ if (task) {
220
+ console.log(` [Triggered]: ${task.name} due to update in ${upstream.name}`);
221
+ console.log('✅ PASS: Dependency chain resolved and task scheduled.');
222
+ } else {
223
+ console.error(`❌ FAIL: Downstream task (${downstream.name}) was NOT scheduled.`);
224
+ console.log(' Dependents Map keys:', Array.from(orch.dependentsByName.keys()));
225
+ }
226
+ } catch (e) {
227
+ console.error('❌ FAIL:', e.message);
228
+ }
229
+ }
230
+
231
+ // EXECUTE
232
+ runRealWorldTests().catch(e => {
233
+ console.error('FATAL:', e);
234
+ });
@@ -43,7 +43,7 @@ async function storeSignedInUserPortfolio({ db, logger, collectionRegistry, cid,
43
43
  date: date,
44
44
  user_id: Number(cid),
45
45
  user_type: 'SIGNED_IN_USER',
46
- portfolio_data: JSON.stringify(portfolioData), // BigQuery JSON type requires a string
46
+ portfolio_data: portfolioData,
47
47
  fetched_at: new Date().toISOString()
48
48
  };
49
49
 
@@ -103,7 +103,7 @@ async function storeSignedInUserTradeHistory({ db, logger, collectionRegistry, c
103
103
  date: date,
104
104
  user_id: Number(cid),
105
105
  user_type: 'SIGNED_IN_USER',
106
- history_data: JSON.stringify(historyData), // BigQuery JSON type requires a string
106
+ history_data: historyData,
107
107
  fetched_at: new Date().toISOString()
108
108
  };
109
109
 
@@ -172,7 +172,7 @@ async function storeSignedInUserSocialPosts({ db, logger, collectionRegistry, ci
172
172
  date: date,
173
173
  user_id: Number(cid),
174
174
  user_type: 'SIGNED_IN_USER',
175
- posts_data: JSON.stringify({ posts: postsMap, postCount: posts.length }), // BigQuery JSON type requires a string
175
+ posts_data: { posts: postsMap, postCount: posts.length },
176
176
  fetched_at: new Date().toISOString()
177
177
  };
178
178
 
@@ -257,7 +257,7 @@ async function storePopularInvestorPortfolio({ db, logger, collectionRegistry, c
257
257
  date: date,
258
258
  user_id: Number(cid),
259
259
  user_type: 'POPULAR_INVESTOR',
260
- portfolio_data: JSON.stringify(portfolioDoc), // BigQuery JSON type requires a string
260
+ portfolio_data: portfolioDoc,
261
261
  fetched_at: new Date().toISOString()
262
262
  };
263
263
 
@@ -316,7 +316,7 @@ async function storePopularInvestorTradeHistory({ db, logger, collectionRegistry
316
316
  date: date,
317
317
  user_id: Number(cid),
318
318
  user_type: 'POPULAR_INVESTOR',
319
- history_data: JSON.stringify(historyData), // BigQuery JSON type requires a string
319
+ history_data: historyData,
320
320
  fetched_at: new Date().toISOString()
321
321
  };
322
322
 
@@ -383,7 +383,7 @@ async function storePopularInvestorSocialPosts({ db, logger, collectionRegistry,
383
383
  date: date,
384
384
  user_id: Number(cid),
385
385
  user_type: 'POPULAR_INVESTOR',
386
- posts_data: JSON.stringify({ posts: postsMap, postCount: posts.length }), // BigQuery JSON type requires a string
386
+ posts_data: { posts: postsMap, postCount: posts.length },
387
387
  fetched_at: new Date().toISOString()
388
388
  };
389
389
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bulltrackers-module",
3
- "version": "1.0.766",
3
+ "version": "1.0.769",
4
4
  "description": "Helper Functions for Bulltrackers.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -1,176 +0,0 @@
1
- /**
2
- * @fileoverview Popular Investor Risk Assessment - v2
3
- *
4
- * This computation DEPENDS on PopularInvestorProfileMetrics.
5
- * It uses the profile metrics to calculate additional risk assessments.
6
- *
7
- * This tests:
8
- * 1. Dependency declaration works
9
- * 2. Pass assignment is incremented (should be Pass 2)
10
- * 3. Dependency data is loaded and passed to process()
11
- */
12
-
13
- const { Computation } = require('../framework');
14
-
15
- class PopularInvestorRiskAssessment extends Computation {
16
-
17
- static getConfig() {
18
- return {
19
- name: 'PopularInvestorRiskAssessment',
20
- category: 'popular_investor',
21
-
22
- // Only needs rankings for additional risk factors
23
- requires: {
24
- 'pi_rankings': {
25
- lookback: 0,
26
- mandatory: true,
27
- filter: null
28
- }
29
- },
30
-
31
- // DEPENDS ON PopularInvestorProfileMetrics!
32
- dependencies: ['PopularInvestorProfileMetrics'],
33
-
34
- type: 'per-entity',
35
- isHistorical: false,
36
- ttlDays: 30
37
- };
38
- }
39
-
40
- static getSchema() {
41
- return {
42
- type: 'object',
43
- properties: {
44
- riskScore: { type: 'number' },
45
- riskLevel: { type: 'string' },
46
- diversificationScore: { type: 'number' },
47
- volatilityScore: { type: 'number' },
48
- concentrationRisk: { type: 'number' },
49
- sectorConcentration: { type: 'object' },
50
- recommendations: { type: 'array' }
51
- }
52
- };
53
- }
54
-
55
- static getWeight() {
56
- return 3.0;
57
- }
58
-
59
- async process(context) {
60
- const { date, entityId: userId, data, dependencies } = context;
61
-
62
- // Get the profile metrics from our dependency
63
- const profileMetrics = dependencies?.['popularinvestorprofilemetrics']?.[userId];
64
-
65
- // Get rankings data
66
- const rankingsData = data['pi_rankings'];
67
- const rankEntry = this._findRankEntry(rankingsData, userId);
68
-
69
- // Initialize result
70
- const result = {
71
- riskScore: 0,
72
- riskLevel: 'Unknown',
73
- diversificationScore: 0,
74
- volatilityScore: 0,
75
- concentrationRisk: 0,
76
- sectorConcentration: {},
77
- recommendations: []
78
- };
79
-
80
- // If we don't have profile metrics, we can still calculate basic risk from rankings
81
- if (rankEntry) {
82
- const riskScore = rankEntry.RiskScore || rankEntry.riskScore || 0;
83
- result.riskScore = riskScore;
84
- result.riskLevel = this._getRiskLevel(riskScore);
85
- }
86
-
87
- // If we have profile metrics, enhance the assessment
88
- if (profileMetrics) {
89
- // Use sector exposure for diversification analysis
90
- const sectorExposure = profileMetrics.sectorExposure?.data || {};
91
- const sectors = Object.keys(sectorExposure);
92
-
93
- if (sectors.length > 0) {
94
- // Diversification score (more sectors = better diversification)
95
- result.diversificationScore = Math.min(100, sectors.length * 15);
96
-
97
- // Concentration risk (highest sector weight)
98
- const maxExposure = Math.max(...Object.values(sectorExposure), 0);
99
- result.concentrationRisk = maxExposure;
100
-
101
- // Copy sector breakdown
102
- result.sectorConcentration = sectorExposure;
103
-
104
- // Generate recommendations
105
- if (maxExposure > 50) {
106
- result.recommendations.push(`High concentration in single sector (${maxExposure.toFixed(1)}%). Consider diversifying.`);
107
- }
108
- if (sectors.length < 3) {
109
- result.recommendations.push('Portfolio is concentrated in few sectors. Consider adding exposure to other sectors.');
110
- }
111
- }
112
-
113
- // Use portfolio summary for additional risk factors
114
- const portfolioSummary = profileMetrics.portfolioSummary || {};
115
- if (portfolioSummary.profitPercent < -10) {
116
- result.recommendations.push('Portfolio showing significant losses. Review position sizing.');
117
- }
118
-
119
- // Use rankings data from profile for win ratio analysis
120
- const rankingsFromProfile = profileMetrics.rankingsData || {};
121
- if (rankingsFromProfile.winRatio < 0.5) {
122
- result.volatilityScore = 80; // Lower win ratio = higher volatility risk
123
- result.recommendations.push('Win ratio below 50%. Trading strategy may need review.');
124
- } else {
125
- result.volatilityScore = Math.max(0, 100 - (rankingsFromProfile.winRatio * 100));
126
- }
127
-
128
- // Adjust overall risk score based on analysis
129
- const adjustedRisk = (
130
- (result.riskScore * 0.4) +
131
- (result.concentrationRisk * 0.3) +
132
- (result.volatilityScore * 0.3)
133
- );
134
- result.riskScore = Math.round(adjustedRisk);
135
- result.riskLevel = this._getRiskLevel(result.riskScore);
136
- }
137
-
138
- this.setResult(userId, result);
139
- }
140
-
141
- _findRankEntry(rankings, userId) {
142
- if (!rankings) return null;
143
-
144
- // Rankings might be date-keyed or an array
145
- if (typeof rankings === 'object' && !Array.isArray(rankings)) {
146
- const dates = Object.keys(rankings).sort().reverse();
147
- for (const dateStr of dates) {
148
- const dayRankings = rankings[dateStr];
149
- if (Array.isArray(dayRankings)) {
150
- const entry = dayRankings.find(r =>
151
- String(r.CustomerId || r.pi_id || r.userId) === String(userId)
152
- );
153
- if (entry) return entry;
154
- }
155
- }
156
- }
157
-
158
- if (Array.isArray(rankings)) {
159
- return rankings.find(r =>
160
- String(r.CustomerId || r.pi_id || r.userId) === String(userId)
161
- );
162
- }
163
-
164
- return null;
165
- }
166
-
167
- _getRiskLevel(score) {
168
- if (score <= 2) return 'Very Low';
169
- if (score <= 4) return 'Low';
170
- if (score <= 6) return 'Medium';
171
- if (score <= 8) return 'High';
172
- return 'Very High';
173
- }
174
- }
175
-
176
- module.exports = PopularInvestorRiskAssessment;