bulltrackers-module 1.0.171 → 1.0.172

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,215 @@
1
+ /**
2
+ * @fileoverview Control Layer - Orchestrates Computation Execution
3
+ * UPDATE: Imported and exposed HistoryExtractor in the math context.
4
+ * UPDATE: Implemented strict User Type segregation. 'All' now defaults to 'Normal'.
5
+ */
6
+
7
+ const {
8
+ DataExtractor,
9
+ HistoryExtractor,
10
+ MathPrimitives,
11
+ Aggregators,
12
+ Validators,
13
+ SCHEMAS,
14
+ SignalPrimitives
15
+ } = require('../layers/math_primitives');
16
+
17
+ const {
18
+ loadDailyInsights,
19
+ loadDailySocialPostInsights,
20
+ getPortfolioPartRefs,
21
+ getHistoryPartRefs,
22
+ streamPortfolioData,
23
+ streamHistoryData
24
+ } = require('../utils/data_loader');
25
+
26
+ // ============================================================================
27
+ // DATA LOADER WRAPPER
28
+ // ============================================================================
29
+
30
+ class DataLoader {
31
+ constructor(config, dependencies) {
32
+ this.config = config;
33
+ this.deps = dependencies;
34
+ this.cache = { mappings: null, insights: new Map(), social: new Map() };
35
+ }
36
+
37
+ async loadMappings() {
38
+ if (this.cache.mappings) return this.cache.mappings;
39
+ const { calculationUtils } = this.deps;
40
+ this.cache.mappings = await calculationUtils.loadInstrumentMappings();
41
+ return this.cache.mappings;
42
+ }
43
+
44
+ async loadInsights(dateStr) {
45
+ if (this.cache.insights.has(dateStr)) return this.cache.insights.get(dateStr);
46
+ const insights = await loadDailyInsights(this.config, this.deps, dateStr);
47
+ this.cache.insights.set(dateStr, insights);
48
+ return insights;
49
+ }
50
+
51
+ async loadSocial(dateStr) {
52
+ if (this.cache.social.has(dateStr)) return this.cache.social.get(dateStr);
53
+ const social = await loadDailySocialPostInsights(this.config, this.deps, dateStr);
54
+ this.cache.social.set(dateStr, social);
55
+ return social;
56
+ }
57
+ }
58
+
59
+ // ============================================================================
60
+ // CONTEXT BUILDER
61
+ // ============================================================================
62
+
63
+ class ContextBuilder {
64
+ static buildPerUserContext(options) {
65
+ const {
66
+ todayPortfolio, yesterdayPortfolio, todayHistory, yesterdayHistory,
67
+ userId, userType, dateStr, metadata, mappings, insights, socialData,
68
+ computedDependencies, config, deps
69
+ } = options;
70
+
71
+ return {
72
+ // User Identity & Data
73
+ user: {
74
+ id: userId,
75
+ type: userType,
76
+ portfolio: { today: todayPortfolio, yesterday: yesterdayPortfolio },
77
+ history: { today: todayHistory, yesterday: yesterdayHistory }
78
+ },
79
+ // Global Time & Data
80
+ date: { today: dateStr },
81
+ insights: { today: insights?.today, yesterday: insights?.yesterday },
82
+ social: { today: socialData?.today, yesterday: socialData?.yesterday },
83
+ // Helpers
84
+ mappings: mappings || {},
85
+ math: {
86
+ extract: DataExtractor,
87
+ history: HistoryExtractor,
88
+ compute: MathPrimitives,
89
+ aggregate: Aggregators,
90
+ validate: Validators,
91
+ signals: SignalPrimitives,
92
+ schemas: SCHEMAS
93
+ },
94
+ computed: computedDependencies || {},
95
+ meta: metadata,
96
+ config,
97
+ deps
98
+ };
99
+ }
100
+
101
+ static buildMetaContext(options) {
102
+ const {
103
+ dateStr, metadata, mappings, insights, socialData,
104
+ computedDependencies, config, deps
105
+ } = options;
106
+
107
+ return {
108
+ date: { today: dateStr },
109
+ insights: { today: insights?.today, yesterday: insights?.yesterday },
110
+ social: { today: socialData?.today, yesterday: socialData?.yesterday },
111
+ mappings: mappings || {},
112
+ math: {
113
+ extract: DataExtractor,
114
+ history: HistoryExtractor,
115
+ compute: MathPrimitives,
116
+ aggregate: Aggregators,
117
+ validate: Validators,
118
+ signals: SignalPrimitives,
119
+ schemas: SCHEMAS
120
+ },
121
+ computed: computedDependencies || {},
122
+ meta: metadata,
123
+ config,
124
+ deps
125
+ };
126
+ }
127
+ }
128
+
129
+ // ============================================================================
130
+ // EXECUTOR
131
+ // ============================================================================
132
+
133
+ class ComputationExecutor {
134
+ constructor(config, dependencies, dataLoader) {
135
+ this.config = config;
136
+ this.deps = dependencies;
137
+ this.loader = dataLoader;
138
+ }
139
+
140
+ async executePerUser(calcInstance, metadata, dateStr, portfolioData, historyData, computedDeps) {
141
+ const { logger } = this.deps;
142
+
143
+ // --------------------------------------------------------------------
144
+ // 1. DETERMINE TARGET SCHEMA
145
+ // --------------------------------------------------------------------
146
+ // We strictly enforce separation here.
147
+ // Unless 'speculator' is explicitly requested, we default to 'normal'.
148
+ // This effectively deprecates 'all' by treating it as 'normal' for safety.
149
+ const targetUserType = (metadata.userType === 'speculator')
150
+ ? SCHEMAS.USER_TYPES.SPECULATOR
151
+ : SCHEMAS.USER_TYPES.NORMAL;
152
+
153
+ const mappings = await this.loader.loadMappings();
154
+ const insights = metadata.rootDataDependencies?.includes('insights')
155
+ ? { today: await this.loader.loadInsights(dateStr) } : null;
156
+
157
+ // Loop through user batch
158
+ for (const [userId, todayPortfolio] of Object.entries(portfolioData)) {
159
+ const yesterdayPortfolio = historyData ? historyData[userId] : null;
160
+
161
+ // ----------------------------------------------------------------
162
+ // 2. IDENTIFY ACTUAL DATA TYPE
163
+ // ----------------------------------------------------------------
164
+ // We inspect the data structure to know what we are holding.
165
+ const actualUserType = todayPortfolio.PublicPositions
166
+ ? SCHEMAS.USER_TYPES.SPECULATOR
167
+ : SCHEMAS.USER_TYPES.NORMAL;
168
+
169
+ // ----------------------------------------------------------------
170
+ // 3. STRICT GATEKEEPING
171
+ // ----------------------------------------------------------------
172
+ // If the computation asked for 'normal' (or 'all'), but we have a 'speculator', SKIP.
173
+ // If the computation asked for 'speculator', but we have a 'normal', SKIP.
174
+ if (targetUserType !== actualUserType) continue;
175
+
176
+ const context = ContextBuilder.buildPerUserContext({
177
+ todayPortfolio, yesterdayPortfolio,
178
+ userId, userType: actualUserType, dateStr, metadata, mappings, insights,
179
+ computedDependencies: computedDeps, config: this.config, deps: this.deps
180
+ });
181
+
182
+ try {
183
+ await calcInstance.process(context);
184
+ } catch (e) {
185
+ logger.log('WARN', `Calc ${metadata.name} failed for user ${userId}: ${e.message}`);
186
+ }
187
+ }
188
+ }
189
+
190
+ async executeOncePerDay(calcInstance, metadata, dateStr, computedDeps) {
191
+ const mappings = await this.loader.loadMappings();
192
+ const insights = metadata.rootDataDependencies?.includes('insights')
193
+ ? { today: await this.loader.loadInsights(dateStr) } : null;
194
+ const social = metadata.rootDataDependencies?.includes('social')
195
+ ? { today: await this.loader.loadSocial(dateStr) } : null;
196
+
197
+ const context = ContextBuilder.buildMetaContext({
198
+ dateStr, metadata, mappings, insights, socialData: social,
199
+ computedDependencies: computedDeps, config: this.config, deps: this.deps
200
+ });
201
+
202
+ return await calcInstance.process(context);
203
+ }
204
+ }
205
+
206
+ class ComputationController {
207
+ constructor(config, dependencies) {
208
+ this.config = config;
209
+ this.deps = dependencies;
210
+ this.loader = new DataLoader(config, dependencies);
211
+ this.executor = new ComputationExecutor(config, dependencies, this.loader);
212
+ }
213
+ }
214
+
215
+ module.exports = { ComputationController };