bulltrackers-module 1.0.768 → 1.0.770
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.
- package/functions/computation-system-v2/UserPortfolioMetrics.js +50 -0
- package/functions/computation-system-v2/computations/BehavioralAnomaly.js +557 -337
- package/functions/computation-system-v2/computations/GlobalAumPerAsset30D.js +103 -0
- package/functions/computation-system-v2/computations/PIDailyAssetAUM.js +134 -0
- package/functions/computation-system-v2/computations/PiFeatureVectors.js +227 -0
- package/functions/computation-system-v2/computations/PiRecommender.js +359 -0
- package/functions/computation-system-v2/computations/RiskScoreIncrease.js +13 -13
- package/functions/computation-system-v2/computations/SignedInUserMirrorHistory.js +138 -0
- package/functions/computation-system-v2/computations/SignedInUserPIProfileMetrics.js +106 -0
- package/functions/computation-system-v2/computations/SignedInUserProfileMetrics.js +324 -0
- package/functions/computation-system-v2/config/bulltrackers.config.js +30 -128
- package/functions/computation-system-v2/core-api.js +17 -9
- package/functions/computation-system-v2/data_schema_reference.MD +108 -0
- package/functions/computation-system-v2/devtools/builder/builder.js +362 -0
- package/functions/computation-system-v2/devtools/builder/examples/user-metrics.yaml +26 -0
- package/functions/computation-system-v2/devtools/index.js +36 -0
- package/functions/computation-system-v2/devtools/shared/MockDataFactory.js +235 -0
- package/functions/computation-system-v2/devtools/shared/SchemaTemplates.js +475 -0
- package/functions/computation-system-v2/devtools/shared/SystemIntrospector.js +517 -0
- package/functions/computation-system-v2/devtools/shared/index.js +16 -0
- package/functions/computation-system-v2/devtools/simulation/DAGAnalyzer.js +243 -0
- package/functions/computation-system-v2/devtools/simulation/MockDataFetcher.js +306 -0
- package/functions/computation-system-v2/devtools/simulation/MockStorageManager.js +336 -0
- package/functions/computation-system-v2/devtools/simulation/SimulationEngine.js +525 -0
- package/functions/computation-system-v2/devtools/simulation/SimulationServer.js +581 -0
- package/functions/computation-system-v2/devtools/simulation/index.js +17 -0
- package/functions/computation-system-v2/devtools/simulation/simulate.js +324 -0
- package/functions/computation-system-v2/devtools/vscode-computation/package.json +90 -0
- package/functions/computation-system-v2/devtools/vscode-computation/snippets/computation.json +128 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/extension.ts +401 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/codeActions.ts +152 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/completions.ts +207 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/diagnostics.ts +205 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/hover.ts +205 -0
- package/functions/computation-system-v2/devtools/vscode-computation/tsconfig.json +22 -0
- package/functions/computation-system-v2/docs/HowToCreateComputations.MD +602 -0
- package/functions/computation-system-v2/framework/data/DataFetcher.js +250 -184
- package/functions/computation-system-v2/framework/data/MaterializedViewManager.js +84 -0
- package/functions/computation-system-v2/framework/data/QueryBuilder.js +38 -38
- package/functions/computation-system-v2/framework/execution/Orchestrator.js +215 -129
- package/functions/computation-system-v2/framework/scheduling/ScheduleValidator.js +17 -19
- package/functions/computation-system-v2/framework/storage/StateRepository.js +32 -2
- package/functions/computation-system-v2/framework/storage/StorageManager.js +105 -67
- package/functions/computation-system-v2/framework/testing/ComputationTester.js +12 -6
- package/functions/computation-system-v2/handlers/dispatcher.js +57 -29
- package/functions/computation-system-v2/handlers/scheduler.js +172 -203
- package/functions/computation-system-v2/legacy/PiAssetRecommender.js.old +115 -0
- package/functions/computation-system-v2/legacy/PiSimilarityMatrix.js +104 -0
- package/functions/computation-system-v2/legacy/PiSimilarityVector.js +71 -0
- package/functions/computation-system-v2/scripts/debug_aggregation.js +25 -0
- package/functions/computation-system-v2/scripts/test-invalidation-scenarios.js +234 -0
- package/package.json +1 -1
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview System Introspector
|
|
3
|
+
*
|
|
4
|
+
* Central intelligence layer for the developer tooling platform.
|
|
5
|
+
* Extracts and exposes knowledge about the computation system:
|
|
6
|
+
* - Available tables and their schemas
|
|
7
|
+
* - Business rule modules and functions
|
|
8
|
+
* - System constraints (lookback limits, valid types)
|
|
9
|
+
* - Computation dependency graph
|
|
10
|
+
*
|
|
11
|
+
* Used by: VS Code Extension, Simulation Engine, Builder
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
|
|
17
|
+
class SystemIntrospector {
|
|
18
|
+
/**
|
|
19
|
+
* @param {Object} config - The bulltrackers.config.js exports
|
|
20
|
+
* @param {Object} [options] - Optional overrides
|
|
21
|
+
* @param {Object} [options.schemaRegistry] - SchemaRegistry instance for live column data
|
|
22
|
+
* @param {Object} [options.rulesRegistry] - RulesRegistry instance for rule metadata
|
|
23
|
+
*/
|
|
24
|
+
constructor(config, options = {}) {
|
|
25
|
+
this.config = config;
|
|
26
|
+
this.schemaRegistry = options.schemaRegistry || null;
|
|
27
|
+
this.rulesRegistry = options.rulesRegistry || null;
|
|
28
|
+
|
|
29
|
+
// Cache for computed values
|
|
30
|
+
this._cache = {
|
|
31
|
+
computationConfigs: null,
|
|
32
|
+
dependencyGraph: null,
|
|
33
|
+
passLevels: null,
|
|
34
|
+
ruleIndex: null
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// System constraints
|
|
38
|
+
this.constraints = {
|
|
39
|
+
maxLookback: 90,
|
|
40
|
+
validTypes: ['global', 'per-entity'],
|
|
41
|
+
validCategories: ['popular_investor', 'signed_in_user', 'global', 'instrument']
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// =========================================================================
|
|
46
|
+
// TABLE INTROSPECTION
|
|
47
|
+
// =========================================================================
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get list of all configured table names.
|
|
51
|
+
* @returns {string[]}
|
|
52
|
+
*/
|
|
53
|
+
getAvailableTables() {
|
|
54
|
+
return Object.keys(this.config.tables || {});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get metadata for a specific table.
|
|
59
|
+
* @param {string} tableName
|
|
60
|
+
* @returns {Object|null} { dateField, entityField, clusterFields, description }
|
|
61
|
+
*/
|
|
62
|
+
getTableMetadata(tableName) {
|
|
63
|
+
const table = this.config.tables?.[tableName];
|
|
64
|
+
if (!table) return null;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
tableName: table.tableName || tableName,
|
|
68
|
+
dateField: table.dateField || null,
|
|
69
|
+
entityField: table.entityField || null,
|
|
70
|
+
clusterFields: table.clusterFields || [],
|
|
71
|
+
description: table.description || null,
|
|
72
|
+
isPartitioned: !!table.dateField
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if a table exists in configuration.
|
|
78
|
+
* @param {string} tableName
|
|
79
|
+
* @returns {boolean}
|
|
80
|
+
*/
|
|
81
|
+
tableExists(tableName) {
|
|
82
|
+
return tableName in (this.config.tables || {});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get table columns. Uses SchemaRegistry if available, otherwise returns null.
|
|
87
|
+
* @param {string} tableName
|
|
88
|
+
* @returns {Promise<string[]|null>}
|
|
89
|
+
*/
|
|
90
|
+
async getTableColumns(tableName) {
|
|
91
|
+
if (!this.schemaRegistry) return null;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const schema = await this.schemaRegistry.getSchema(tableName);
|
|
95
|
+
return schema?.columns?.map(c => c.name) || null;
|
|
96
|
+
} catch (e) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Validate that a column exists in a table.
|
|
103
|
+
* @param {string} tableName
|
|
104
|
+
* @param {string} columnName
|
|
105
|
+
* @returns {Promise<boolean>}
|
|
106
|
+
*/
|
|
107
|
+
async columnExists(tableName, columnName) {
|
|
108
|
+
const columns = await this.getTableColumns(tableName);
|
|
109
|
+
if (!columns) return true; // Can't validate without schema, assume valid
|
|
110
|
+
return columns.includes(columnName);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// =========================================================================
|
|
114
|
+
// RULE INTROSPECTION
|
|
115
|
+
// =========================================================================
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get list of all rule module names.
|
|
119
|
+
* @returns {string[]}
|
|
120
|
+
*/
|
|
121
|
+
getRuleModules() {
|
|
122
|
+
const rules = this.config.rules || {};
|
|
123
|
+
return Object.keys(rules);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get list of functions exported by a rule module.
|
|
128
|
+
* @param {string} moduleName
|
|
129
|
+
* @returns {string[]}
|
|
130
|
+
*/
|
|
131
|
+
getRuleFunctions(moduleName) {
|
|
132
|
+
const module = this.config.rules?.[moduleName];
|
|
133
|
+
if (!module) return [];
|
|
134
|
+
|
|
135
|
+
return Object.keys(module).filter(key => typeof module[key] === 'function');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if a rule function exists.
|
|
140
|
+
* @param {string} moduleName
|
|
141
|
+
* @param {string} functionName
|
|
142
|
+
* @returns {boolean}
|
|
143
|
+
*/
|
|
144
|
+
ruleFunctionExists(moduleName, functionName) {
|
|
145
|
+
const module = this.config.rules?.[moduleName];
|
|
146
|
+
if (!module) return false;
|
|
147
|
+
return typeof module[functionName] === 'function';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get all rule functions as a flat index.
|
|
152
|
+
* @returns {Map<string, { module: string, path: string }>}
|
|
153
|
+
*/
|
|
154
|
+
getRuleIndex() {
|
|
155
|
+
if (this._cache.ruleIndex) return this._cache.ruleIndex;
|
|
156
|
+
|
|
157
|
+
const index = new Map();
|
|
158
|
+
for (const moduleName of this.getRuleModules()) {
|
|
159
|
+
for (const funcName of this.getRuleFunctions(moduleName)) {
|
|
160
|
+
index.set(funcName, {
|
|
161
|
+
module: moduleName,
|
|
162
|
+
path: `rules.${moduleName}.${funcName}`
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this._cache.ruleIndex = index;
|
|
168
|
+
return index;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Find the closest matching rule function (for "did you mean" suggestions).
|
|
173
|
+
* @param {string} moduleName
|
|
174
|
+
* @param {string} attemptedName
|
|
175
|
+
* @returns {string|null}
|
|
176
|
+
*/
|
|
177
|
+
findClosestRuleFunction(moduleName, attemptedName) {
|
|
178
|
+
const functions = this.getRuleFunctions(moduleName);
|
|
179
|
+
if (functions.length === 0) return null;
|
|
180
|
+
|
|
181
|
+
// Simple Levenshtein-based matching
|
|
182
|
+
let closest = null;
|
|
183
|
+
let minDistance = Infinity;
|
|
184
|
+
|
|
185
|
+
for (const fn of functions) {
|
|
186
|
+
const distance = this._levenshtein(attemptedName.toLowerCase(), fn.toLowerCase());
|
|
187
|
+
if (distance < minDistance && distance <= 3) {
|
|
188
|
+
minDistance = distance;
|
|
189
|
+
closest = fn;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return closest;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Find the closest matching table name.
|
|
198
|
+
* @param {string} attemptedName
|
|
199
|
+
* @returns {string|null}
|
|
200
|
+
*/
|
|
201
|
+
findClosestTable(attemptedName) {
|
|
202
|
+
const tables = this.getAvailableTables();
|
|
203
|
+
let closest = null;
|
|
204
|
+
let minDistance = Infinity;
|
|
205
|
+
|
|
206
|
+
for (const table of tables) {
|
|
207
|
+
const distance = this._levenshtein(attemptedName.toLowerCase(), table.toLowerCase());
|
|
208
|
+
if (distance < minDistance && distance <= 5) {
|
|
209
|
+
minDistance = distance;
|
|
210
|
+
closest = table;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return closest;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// =========================================================================
|
|
218
|
+
// CONSTRAINT INTROSPECTION
|
|
219
|
+
// =========================================================================
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get the maximum allowed lookback value.
|
|
223
|
+
* @returns {number}
|
|
224
|
+
*/
|
|
225
|
+
getMaxLookback() {
|
|
226
|
+
return this.constraints.maxLookback;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get valid computation types.
|
|
231
|
+
* @returns {string[]}
|
|
232
|
+
*/
|
|
233
|
+
getValidTypes() {
|
|
234
|
+
return [...this.constraints.validTypes];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get valid computation categories.
|
|
239
|
+
* @returns {string[]}
|
|
240
|
+
*/
|
|
241
|
+
getValidCategories() {
|
|
242
|
+
// Extract from existing computations + defaults
|
|
243
|
+
const categories = new Set(this.constraints.validCategories);
|
|
244
|
+
|
|
245
|
+
for (const comp of this.config.computations || []) {
|
|
246
|
+
try {
|
|
247
|
+
const cfg = comp.getConfig();
|
|
248
|
+
if (cfg.category) categories.add(cfg.category);
|
|
249
|
+
} catch (e) { /* skip */ }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return Array.from(categories);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// =========================================================================
|
|
256
|
+
// COMPUTATION INTROSPECTION
|
|
257
|
+
// =========================================================================
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Get list of all computation names.
|
|
261
|
+
* @returns {string[]}
|
|
262
|
+
*/
|
|
263
|
+
getComputationNames() {
|
|
264
|
+
const names = [];
|
|
265
|
+
for (const comp of this.config.computations || []) {
|
|
266
|
+
try {
|
|
267
|
+
names.push(comp.getConfig().name);
|
|
268
|
+
} catch (e) { /* skip */ }
|
|
269
|
+
}
|
|
270
|
+
return names;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get config for a specific computation.
|
|
275
|
+
* @param {string} name
|
|
276
|
+
* @returns {Object|null}
|
|
277
|
+
*/
|
|
278
|
+
getComputationConfig(name) {
|
|
279
|
+
for (const comp of this.config.computations || []) {
|
|
280
|
+
try {
|
|
281
|
+
const cfg = comp.getConfig();
|
|
282
|
+
if (cfg.name === name) return cfg;
|
|
283
|
+
} catch (e) { /* skip */ }
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Get all computation configs, cached.
|
|
290
|
+
* @returns {Map<string, Object>}
|
|
291
|
+
*/
|
|
292
|
+
getAllComputationConfigs() {
|
|
293
|
+
if (this._cache.computationConfigs) return this._cache.computationConfigs;
|
|
294
|
+
|
|
295
|
+
const map = new Map();
|
|
296
|
+
for (const comp of this.config.computations || []) {
|
|
297
|
+
try {
|
|
298
|
+
const cfg = comp.getConfig();
|
|
299
|
+
map.set(cfg.name, cfg);
|
|
300
|
+
} catch (e) { /* skip */ }
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
this._cache.computationConfigs = map;
|
|
304
|
+
return map;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Build dependency graph from computation configs.
|
|
309
|
+
* @returns {Map<string, string[]>} computation name -> dependencies
|
|
310
|
+
*/
|
|
311
|
+
getDependencyGraph() {
|
|
312
|
+
if (this._cache.dependencyGraph) return this._cache.dependencyGraph;
|
|
313
|
+
|
|
314
|
+
const graph = new Map();
|
|
315
|
+
const configs = this.getAllComputationConfigs();
|
|
316
|
+
|
|
317
|
+
for (const [name, cfg] of configs) {
|
|
318
|
+
const deps = [];
|
|
319
|
+
|
|
320
|
+
// Explicit dependencies
|
|
321
|
+
if (cfg.dependencies) {
|
|
322
|
+
deps.push(...cfg.dependencies);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Conditional dependencies
|
|
326
|
+
if (cfg.conditionalDependencies) {
|
|
327
|
+
for (const cd of cfg.conditionalDependencies) {
|
|
328
|
+
if (cd.computation) deps.push(cd.computation);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
graph.set(name, deps);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this._cache.dependencyGraph = graph;
|
|
336
|
+
return graph;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Calculate pass levels (topological depth) for all computations.
|
|
341
|
+
* @returns {Map<string, number>}
|
|
342
|
+
*/
|
|
343
|
+
getPassLevels() {
|
|
344
|
+
if (this._cache.passLevels) return this._cache.passLevels;
|
|
345
|
+
|
|
346
|
+
const graph = this.getDependencyGraph();
|
|
347
|
+
const levels = new Map();
|
|
348
|
+
const visited = new Set();
|
|
349
|
+
|
|
350
|
+
const calculateLevel = (name) => {
|
|
351
|
+
if (levels.has(name)) return levels.get(name);
|
|
352
|
+
if (visited.has(name)) return 0; // Cycle detected
|
|
353
|
+
visited.add(name);
|
|
354
|
+
|
|
355
|
+
const deps = graph.get(name) || [];
|
|
356
|
+
if (deps.length === 0) {
|
|
357
|
+
levels.set(name, 1);
|
|
358
|
+
return 1;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let maxDepLevel = 0;
|
|
362
|
+
for (const dep of deps) {
|
|
363
|
+
maxDepLevel = Math.max(maxDepLevel, calculateLevel(dep));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const level = maxDepLevel + 1;
|
|
367
|
+
levels.set(name, level);
|
|
368
|
+
return level;
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
for (const name of graph.keys()) {
|
|
372
|
+
calculateLevel(name);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
this._cache.passLevels = levels;
|
|
376
|
+
return levels;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Get the pass level for a specific computation.
|
|
381
|
+
* @param {string} name
|
|
382
|
+
* @returns {number}
|
|
383
|
+
*/
|
|
384
|
+
getPassLevel(name) {
|
|
385
|
+
return this.getPassLevels().get(name) || 1;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Get total number of passes needed to run all computations.
|
|
390
|
+
* @returns {number}
|
|
391
|
+
*/
|
|
392
|
+
getTotalPasses() {
|
|
393
|
+
const levels = this.getPassLevels();
|
|
394
|
+
return Math.max(...levels.values(), 1);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// =========================================================================
|
|
398
|
+
// VALIDATION HELPERS
|
|
399
|
+
// =========================================================================
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Validate a computation config object.
|
|
403
|
+
* @param {Object} config
|
|
404
|
+
* @returns {{ valid: boolean, errors: string[], warnings: string[] }}
|
|
405
|
+
*/
|
|
406
|
+
validateComputationConfig(config) {
|
|
407
|
+
const errors = [];
|
|
408
|
+
const warnings = [];
|
|
409
|
+
|
|
410
|
+
// Required fields
|
|
411
|
+
if (!config.name) errors.push('Missing required field: name');
|
|
412
|
+
if (!config.requires) errors.push('Missing required field: requires');
|
|
413
|
+
|
|
414
|
+
// Type validation
|
|
415
|
+
if (config.type && !this.constraints.validTypes.includes(config.type)) {
|
|
416
|
+
errors.push(`Invalid type: "${config.type}". Must be one of: ${this.constraints.validTypes.join(', ')}`);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Table validation
|
|
420
|
+
if (config.requires) {
|
|
421
|
+
for (const tableName of Object.keys(config.requires)) {
|
|
422
|
+
if (!this.tableExists(tableName)) {
|
|
423
|
+
const closest = this.findClosestTable(tableName);
|
|
424
|
+
const suggestion = closest ? ` Did you mean "${closest}"?` : '';
|
|
425
|
+
errors.push(`Unknown table: "${tableName}".${suggestion}`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Lookback validation
|
|
429
|
+
const tableReq = config.requires[tableName];
|
|
430
|
+
if (tableReq.lookback !== undefined) {
|
|
431
|
+
if (tableReq.lookback > this.constraints.maxLookback) {
|
|
432
|
+
errors.push(`Lookback ${tableReq.lookback} for "${tableName}" exceeds maximum of ${this.constraints.maxLookback}`);
|
|
433
|
+
} else if (tableReq.lookback > this.constraints.maxLookback * 0.7) {
|
|
434
|
+
warnings.push(`Lookback ${tableReq.lookback} for "${tableName}" is ${Math.round(tableReq.lookback / this.constraints.maxLookback * 100)}% of maximum`);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Check lookback on non-partitioned tables
|
|
438
|
+
const meta = this.getTableMetadata(tableName);
|
|
439
|
+
if (meta && !meta.isPartitioned && tableReq.lookback > 0) {
|
|
440
|
+
warnings.push(`Lookback on non-partitioned table "${tableName}" will fetch all rows`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Dependency validation
|
|
447
|
+
if (config.dependencies) {
|
|
448
|
+
for (const dep of config.dependencies) {
|
|
449
|
+
if (!this.getComputationNames().includes(dep)) {
|
|
450
|
+
errors.push(`Unknown dependency: "${dep}"`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// =========================================================================
|
|
459
|
+
// HELPERS
|
|
460
|
+
// =========================================================================
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Levenshtein distance for fuzzy matching.
|
|
464
|
+
*/
|
|
465
|
+
_levenshtein(a, b) {
|
|
466
|
+
const matrix = [];
|
|
467
|
+
for (let i = 0; i <= b.length; i++) {
|
|
468
|
+
matrix[i] = [i];
|
|
469
|
+
}
|
|
470
|
+
for (let j = 0; j <= a.length; j++) {
|
|
471
|
+
matrix[0][j] = j;
|
|
472
|
+
}
|
|
473
|
+
for (let i = 1; i <= b.length; i++) {
|
|
474
|
+
for (let j = 1; j <= a.length; j++) {
|
|
475
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
476
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
477
|
+
} else {
|
|
478
|
+
matrix[i][j] = Math.min(
|
|
479
|
+
matrix[i - 1][j - 1] + 1,
|
|
480
|
+
matrix[i][j - 1] + 1,
|
|
481
|
+
matrix[i - 1][j] + 1
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return matrix[b.length][a.length];
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Clear all caches.
|
|
491
|
+
*/
|
|
492
|
+
clearCache() {
|
|
493
|
+
this._cache = {
|
|
494
|
+
computationConfigs: null,
|
|
495
|
+
dependencyGraph: null,
|
|
496
|
+
passLevels: null,
|
|
497
|
+
ruleIndex: null
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Get summary statistics about the system.
|
|
503
|
+
* @returns {Object}
|
|
504
|
+
*/
|
|
505
|
+
getStats() {
|
|
506
|
+
return {
|
|
507
|
+
tables: this.getAvailableTables().length,
|
|
508
|
+
ruleModules: this.getRuleModules().length,
|
|
509
|
+
ruleFunctions: this.getRuleIndex().size,
|
|
510
|
+
computations: this.getComputationNames().length,
|
|
511
|
+
totalPasses: this.getTotalPasses(),
|
|
512
|
+
maxLookback: this.constraints.maxLookback
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
module.exports = { SystemIntrospector };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared DevTools Module Index
|
|
3
|
+
*
|
|
4
|
+
* Central exports for the developer tooling shared layer.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { SystemIntrospector } = require('./SystemIntrospector');
|
|
8
|
+
const { MockDataFactory } = require('./MockDataFactory');
|
|
9
|
+
const { SchemaTemplates, helpers: schemaHelpers } = require('./SchemaTemplates');
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
SystemIntrospector,
|
|
13
|
+
MockDataFactory,
|
|
14
|
+
SchemaTemplates,
|
|
15
|
+
schemaHelpers
|
|
16
|
+
};
|