bulltrackers-module 1.0.762 → 1.0.764

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.
@@ -0,0 +1,140 @@
1
+ const { Computation } = require('../framework');
2
+
3
+ class RiskScoreIncrease extends Computation {
4
+
5
+ static getConfig() {
6
+ return {
7
+ name: 'RiskScoreIncrease',
8
+ // V2 Migration: Switch to per-entity for parallel processing
9
+ type: 'per-entity',
10
+ category: 'alerts',
11
+ isHistorical: true,
12
+
13
+ // V2 Migration: Define strict data requirements
14
+ requires: {
15
+ 'rankings': {
16
+ lookback: 1, // Fetches Today + Yesterday
17
+ mandatory: true,
18
+ // Optimization: Only fetch needed fields
19
+ fields: ['user_id', 'rankings_data', 'date'],
20
+ // Filter applied at data fetch level (replacing V1 userType logic)
21
+ filter: { user_type: 'POPULAR_INVESTOR' }
22
+ }
23
+ },
24
+
25
+ // V2 Migration: Explicit storage configuration for backward compatibility
26
+ storage: {
27
+ bigquery: true,
28
+ firestore: {
29
+ enabled: true,
30
+ // Ensures V1 alert system can find the document
31
+ path: 'alerts/{date}/RiskScoreIncrease/{entityId}',
32
+ merge: true
33
+ }
34
+ },
35
+
36
+ // ============================================================
37
+ // LEGACY METADATA PRESERVED EXACTLY
38
+ // This section is read by the external Alert System
39
+ // ============================================================
40
+ userType: 'POPULAR_INVESTOR',
41
+ alert: {
42
+ id: 'increasedRisk',
43
+ frontendName: 'Increased Risk',
44
+ description: 'Alert when a Popular Investor\'s risk score increases',
45
+ messageTemplate: '{piUsername}\'s risk score increased by {change} points (from {previous} to {current})',
46
+ severity: 'high',
47
+ configKey: 'increasedRisk',
48
+ isDynamic: true,
49
+ dynamicConfig: {
50
+ thresholds: [
51
+ {
52
+ key: 'minChange',
53
+ type: 'number',
54
+ label: 'Minimum risk increase',
55
+ description: 'Only alert if risk increases by at least this many points',
56
+ default: 0,
57
+ min: 0,
58
+ max: 10,
59
+ step: 0.5,
60
+ unit: 'points'
61
+ },
62
+ {
63
+ key: 'minRiskLevel',
64
+ type: 'number',
65
+ label: 'Minimum risk level',
66
+ description: 'Only alert if new risk score is above this level',
67
+ default: 0,
68
+ min: 0,
69
+ max: 10,
70
+ step: 0.5,
71
+ unit: 'risk score'
72
+ }
73
+ ],
74
+ resultFields: {
75
+ change: 'change',
76
+ newValue: 'currentRisk',
77
+ oldValue: 'previousRisk'
78
+ }
79
+ }
80
+ }
81
+ };
82
+ }
83
+
84
+ async process(context) {
85
+ const { data, entityId, date, rules } = context;
86
+
87
+ // 1. Data Access
88
+ // In V2 with lookback: 1, this is an array of entries sorted by date
89
+ const rankingHistory = data['rankings'] || [];
90
+
91
+ // Find today's and yesterday's entries specifically by date string
92
+ const todayEntry = rankingHistory.find(d => d.date === date);
93
+
94
+ // Calculate yesterday's date string for lookup
95
+ const yesterdayDate = new Date(date);
96
+ yesterdayDate.setDate(yesterdayDate.getDate() - 1);
97
+ const yesterdayStr = yesterdayDate.toISOString().split('T')[0];
98
+
99
+ const yesterdayEntry = rankingHistory.find(d => d.date === yesterdayStr);
100
+
101
+ // 2. Business Logic (Using Rules)
102
+ // Extract raw JSON data using rules
103
+ const todayData = rules.rankings.extractRankingsData(todayEntry);
104
+ const yesterdayData = rules.rankings.extractRankingsData(yesterdayEntry);
105
+
106
+ // Using rules.rankings to safely extract the score
107
+ // getRiskScore(data)
108
+ const currentRisk = todayData ? rules.rankings.getRiskScore(todayData) : null;
109
+ const previousRisk = yesterdayData ? rules.rankings.getRiskScore(yesterdayData) : null;
110
+
111
+ // 3. Validation
112
+ // If today's risk data is missing, we cannot evaluate
113
+ if (currentRisk === null || currentRisk === undefined) {
114
+ this.log('WARN', `No current risk data for ${entityId}`);
115
+ return;
116
+ }
117
+
118
+ // 4. Comparison Logic
119
+ const isBaselineReset = (previousRisk === null || previousRisk === undefined);
120
+ const change = isBaselineReset ? 0 : (currentRisk - previousRisk);
121
+ const hasIncreased = !isBaselineReset && (currentRisk > previousRisk);
122
+
123
+ // 5. Construct Result
124
+ // Matches the structure required by the "alert.dynamicConfig.resultFields"
125
+ const result = {
126
+ currentRisk,
127
+ previousRisk: isBaselineReset ? 'N/A' : previousRisk,
128
+ change: change,
129
+ isBaselineReset,
130
+ triggered: hasIncreased, // Helpful flag for query filtering
131
+ updatedAt: new Date().toISOString()
132
+ };
133
+
134
+ // 6. Save Result
135
+ // This writes to BigQuery AND the Firestore path defined in getConfig
136
+ this.setResult(entityId, result);
137
+ }
138
+ }
139
+
140
+ module.exports = RiskScoreIncrease;
@@ -11,6 +11,27 @@
11
11
  // Load business rules
12
12
  const rules = require('../rules');
13
13
 
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ // Dynamic Computation Loader
18
+ function loadComputations() {
19
+ const computationsDir = path.join(__dirname, '../computations');
20
+ const files = fs.readdirSync(computationsDir);
21
+
22
+ return files
23
+ .filter(file => file.endsWith('.js') && !file.startsWith('_')) // Skip hidden/test files
24
+ .map(file => {
25
+ try {
26
+ return require(path.join(computationsDir, file));
27
+ } catch (e) {
28
+ console.error(`[Config] Failed to load computation ${file}:`, e.message);
29
+ return null;
30
+ }
31
+ })
32
+ .filter(Boolean); // Remove nulls
33
+ }
34
+
14
35
  module.exports = {
15
36
  // =========================================================================
16
37
  // PROJECT CONFIGURATION
@@ -201,10 +222,7 @@ module.exports = {
201
222
  // by one as they're migrated from v1.
202
223
  // =========================================================================
203
224
 
204
- computations: [
205
- // Add migrated computations here:
206
- // require('../computations/UserRiskScore'),
207
- ],
225
+ computations: loadComputations(),
208
226
 
209
227
  // =========================================================================
210
228
  // PREDEFINED FILTER SETS
@@ -9,15 +9,6 @@ const config = require('./config/bulltrackers.config');
9
9
  const { ManifestBuilder } = require('./framework/core/Manifest');
10
10
  const { Computation } = require('./framework/core/Computation');
11
11
 
12
- // Add computations to config
13
- config.computations = [
14
- require('./computations/UserPortfolioSummary'),
15
- require('./computations/PopularInvestorProfileMetrics'),
16
- require('./computations/PopularInvestorRiskAssessment'),
17
- require('./computations/PopularInvestorRiskMetrics'),
18
- // Add more computations here as they're migrated
19
- ];
20
-
21
12
  // Singleton orchestrator instance
22
13
  let orchestrator = null;
23
14