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.
Files changed (52) hide show
  1. package/functions/computation-system-v2/UserPortfolioMetrics.js +50 -0
  2. package/functions/computation-system-v2/computations/BehavioralAnomaly.js +557 -337
  3. package/functions/computation-system-v2/computations/GlobalAumPerAsset30D.js +103 -0
  4. package/functions/computation-system-v2/computations/PIDailyAssetAUM.js +134 -0
  5. package/functions/computation-system-v2/computations/PiFeatureVectors.js +227 -0
  6. package/functions/computation-system-v2/computations/PiRecommender.js +359 -0
  7. package/functions/computation-system-v2/computations/RiskScoreIncrease.js +13 -13
  8. package/functions/computation-system-v2/computations/SignedInUserMirrorHistory.js +138 -0
  9. package/functions/computation-system-v2/computations/SignedInUserPIProfileMetrics.js +106 -0
  10. package/functions/computation-system-v2/computations/SignedInUserProfileMetrics.js +324 -0
  11. package/functions/computation-system-v2/config/bulltrackers.config.js +30 -128
  12. package/functions/computation-system-v2/core-api.js +17 -9
  13. package/functions/computation-system-v2/data_schema_reference.MD +108 -0
  14. package/functions/computation-system-v2/devtools/builder/builder.js +362 -0
  15. package/functions/computation-system-v2/devtools/builder/examples/user-metrics.yaml +26 -0
  16. package/functions/computation-system-v2/devtools/index.js +36 -0
  17. package/functions/computation-system-v2/devtools/shared/MockDataFactory.js +235 -0
  18. package/functions/computation-system-v2/devtools/shared/SchemaTemplates.js +475 -0
  19. package/functions/computation-system-v2/devtools/shared/SystemIntrospector.js +517 -0
  20. package/functions/computation-system-v2/devtools/shared/index.js +16 -0
  21. package/functions/computation-system-v2/devtools/simulation/DAGAnalyzer.js +243 -0
  22. package/functions/computation-system-v2/devtools/simulation/MockDataFetcher.js +306 -0
  23. package/functions/computation-system-v2/devtools/simulation/MockStorageManager.js +336 -0
  24. package/functions/computation-system-v2/devtools/simulation/SimulationEngine.js +525 -0
  25. package/functions/computation-system-v2/devtools/simulation/SimulationServer.js +581 -0
  26. package/functions/computation-system-v2/devtools/simulation/index.js +17 -0
  27. package/functions/computation-system-v2/devtools/simulation/simulate.js +324 -0
  28. package/functions/computation-system-v2/devtools/vscode-computation/package.json +90 -0
  29. package/functions/computation-system-v2/devtools/vscode-computation/snippets/computation.json +128 -0
  30. package/functions/computation-system-v2/devtools/vscode-computation/src/extension.ts +401 -0
  31. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/codeActions.ts +152 -0
  32. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/completions.ts +207 -0
  33. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/diagnostics.ts +205 -0
  34. package/functions/computation-system-v2/devtools/vscode-computation/src/providers/hover.ts +205 -0
  35. package/functions/computation-system-v2/devtools/vscode-computation/tsconfig.json +22 -0
  36. package/functions/computation-system-v2/docs/HowToCreateComputations.MD +602 -0
  37. package/functions/computation-system-v2/framework/data/DataFetcher.js +250 -184
  38. package/functions/computation-system-v2/framework/data/MaterializedViewManager.js +84 -0
  39. package/functions/computation-system-v2/framework/data/QueryBuilder.js +38 -38
  40. package/functions/computation-system-v2/framework/execution/Orchestrator.js +215 -129
  41. package/functions/computation-system-v2/framework/scheduling/ScheduleValidator.js +17 -19
  42. package/functions/computation-system-v2/framework/storage/StateRepository.js +32 -2
  43. package/functions/computation-system-v2/framework/storage/StorageManager.js +105 -67
  44. package/functions/computation-system-v2/framework/testing/ComputationTester.js +12 -6
  45. package/functions/computation-system-v2/handlers/dispatcher.js +57 -29
  46. package/functions/computation-system-v2/handlers/scheduler.js +172 -203
  47. package/functions/computation-system-v2/legacy/PiAssetRecommender.js.old +115 -0
  48. package/functions/computation-system-v2/legacy/PiSimilarityMatrix.js +104 -0
  49. package/functions/computation-system-v2/legacy/PiSimilarityVector.js +71 -0
  50. package/functions/computation-system-v2/scripts/debug_aggregation.js +25 -0
  51. package/functions/computation-system-v2/scripts/test-invalidation-scenarios.js +234 -0
  52. package/package.json +1 -1
@@ -0,0 +1,401 @@
1
+ /**
2
+ * @fileoverview VS Code Extension Entry Point
3
+ *
4
+ * Provides:
5
+ * - Autocomplete for tables, fields, rules
6
+ * - Diagnostics for validation errors
7
+ * - Hover documentation
8
+ * - Code actions (quick fixes)
9
+ * - Status bar for DAG position
10
+ */
11
+
12
+ import * as vscode from 'vscode';
13
+ import * as path from 'path';
14
+ import * as fs from 'fs';
15
+
16
+ // Providers
17
+ import { ComputationCompletionProvider } from './providers/completions';
18
+ import { ComputationDiagnosticsProvider } from './providers/diagnostics';
19
+ import { ComputationHoverProvider } from './providers/hover';
20
+ import { ComputationCodeActionProvider } from './providers/codeActions';
21
+
22
+ // State
23
+ let diagnosticsProvider: ComputationDiagnosticsProvider | null = null;
24
+ let statusBarItem: vscode.StatusBarItem | null = null;
25
+
26
+ export function activate(context: vscode.ExtensionContext) {
27
+ console.log('Computation Developer extension is now active');
28
+
29
+ // Load system knowledge
30
+ const systemKnowledge = loadSystemKnowledge();
31
+ if (!systemKnowledge) {
32
+ vscode.window.showWarningMessage(
33
+ 'Computation Developer: Could not load system configuration. Some features may be limited.'
34
+ );
35
+ }
36
+
37
+ // Register completion provider
38
+ const completionProvider = new ComputationCompletionProvider(systemKnowledge);
39
+ context.subscriptions.push(
40
+ vscode.languages.registerCompletionItemProvider(
41
+ { language: 'javascript', pattern: '**/computation*/**/*.js' },
42
+ completionProvider,
43
+ '.', "'", '"' // Trigger characters
44
+ )
45
+ );
46
+
47
+ // Register diagnostics provider
48
+ diagnosticsProvider = new ComputationDiagnosticsProvider(systemKnowledge);
49
+ context.subscriptions.push(diagnosticsProvider);
50
+
51
+ // Register hover provider
52
+ const hoverProvider = new ComputationHoverProvider(systemKnowledge);
53
+ context.subscriptions.push(
54
+ vscode.languages.registerHoverProvider(
55
+ { language: 'javascript', pattern: '**/computation*/**/*.js' },
56
+ hoverProvider
57
+ )
58
+ );
59
+
60
+ // Register code action provider
61
+ const codeActionProvider = new ComputationCodeActionProvider(systemKnowledge);
62
+ context.subscriptions.push(
63
+ vscode.languages.registerCodeActionsProvider(
64
+ { language: 'javascript', pattern: '**/computation*/**/*.js' },
65
+ codeActionProvider,
66
+ { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }
67
+ )
68
+ );
69
+
70
+ // Create status bar item
71
+ statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
72
+ statusBarItem.command = 'computationDeveloper.showDAG';
73
+ context.subscriptions.push(statusBarItem);
74
+
75
+ // Update status bar on active editor change
76
+ context.subscriptions.push(
77
+ vscode.window.onDidChangeActiveTextEditor(updateStatusBar)
78
+ );
79
+ updateStatusBar(vscode.window.activeTextEditor);
80
+
81
+ // Register commands
82
+ context.subscriptions.push(
83
+ vscode.commands.registerCommand('computationDeveloper.validate', () => {
84
+ if (diagnosticsProvider && vscode.window.activeTextEditor) {
85
+ diagnosticsProvider.validateDocument(vscode.window.activeTextEditor.document);
86
+ vscode.window.showInformationMessage('Validation complete');
87
+ }
88
+ })
89
+ );
90
+
91
+ context.subscriptions.push(
92
+ vscode.commands.registerCommand('computationDeveloper.simulate', async () => {
93
+ const editor = vscode.window.activeTextEditor;
94
+ if (!editor) return;
95
+
96
+ const computationName = extractComputationName(editor.document.getText());
97
+ if (!computationName) {
98
+ vscode.window.showErrorMessage('Could not identify computation name');
99
+ return;
100
+ }
101
+
102
+ const config = vscode.workspace.getConfiguration('computationDeveloper');
103
+ const serverUrl = config.get<string>('simulationServerUrl') || 'http://localhost:3210';
104
+
105
+ try {
106
+ const response = await fetch(`${serverUrl}/api/simulate`, {
107
+ method: 'POST',
108
+ headers: { 'Content-Type': 'application/json' },
109
+ body: JSON.stringify({ computation: computationName })
110
+ });
111
+ const result = await response.json();
112
+
113
+ const output = vscode.window.createOutputChannel('Computation Simulation');
114
+ output.clear();
115
+ output.appendLine(`Simulation: ${computationName}`);
116
+ output.appendLine(`Status: ${result.success ? '✓ Success' : '✗ Failed'}`);
117
+ if (result.stats) {
118
+ output.appendLine(`Time: ${result.stats.executionTimeMs}ms`);
119
+ output.appendLine(`Entities: ${result.stats.entitiesProcessed}`);
120
+ }
121
+ output.appendLine('\nResults:');
122
+ output.appendLine(JSON.stringify(result.results || result, null, 2));
123
+ output.show();
124
+ } catch (e: any) {
125
+ vscode.window.showErrorMessage(`Simulation failed: ${e.message}. Is the server running?`);
126
+ }
127
+ })
128
+ );
129
+
130
+ context.subscriptions.push(
131
+ vscode.commands.registerCommand('computationDeveloper.showDAG', async () => {
132
+ const editor = vscode.window.activeTextEditor;
133
+ if (!editor) return;
134
+
135
+ const computationName = extractComputationName(editor.document.getText());
136
+ if (!computationName) {
137
+ vscode.window.showErrorMessage('Could not identify computation name');
138
+ return;
139
+ }
140
+
141
+ const config = vscode.workspace.getConfiguration('computationDeveloper');
142
+ const serverUrl = config.get<string>('simulationServerUrl') || 'http://localhost:3210';
143
+
144
+ try {
145
+ const response = await fetch(`${serverUrl}/api/info?computation=${computationName}`);
146
+ const info = await response.json();
147
+
148
+ const panel = vscode.window.createWebviewPanel(
149
+ 'dagView',
150
+ `DAG: ${computationName}`,
151
+ vscode.ViewColumn.Beside,
152
+ {}
153
+ );
154
+ panel.webview.html = getDAGWebviewContent(info);
155
+ } catch (e: any) {
156
+ vscode.window.showErrorMessage(`Could not load DAG info: ${e.message}`);
157
+ }
158
+ })
159
+ );
160
+
161
+ context.subscriptions.push(
162
+ vscode.commands.registerCommand('computationDeveloper.generateBoilerplate', async () => {
163
+ const name = await vscode.window.showInputBox({
164
+ prompt: 'Computation name (e.g., UserProfileMetrics)',
165
+ validateInput: (value) => {
166
+ if (!value || !/^[A-Z][a-zA-Z0-9]+$/.test(value)) {
167
+ return 'Name must be PascalCase';
168
+ }
169
+ return null;
170
+ }
171
+ });
172
+ if (!name) return;
173
+
174
+ const type = await vscode.window.showQuickPick(['per-entity', 'global'], {
175
+ placeHolder: 'Computation type'
176
+ });
177
+ if (!type) return;
178
+
179
+ const boilerplate = generateBoilerplate(name, type);
180
+
181
+ const doc = await vscode.workspace.openTextDocument({
182
+ content: boilerplate,
183
+ language: 'javascript'
184
+ });
185
+ await vscode.window.showTextDocument(doc);
186
+ })
187
+ );
188
+
189
+ // Watch for document changes
190
+ context.subscriptions.push(
191
+ vscode.workspace.onDidChangeTextDocument(event => {
192
+ if (event.document.languageId === 'javascript') {
193
+ diagnosticsProvider?.validateDocument(event.document);
194
+ }
195
+ })
196
+ );
197
+
198
+ // Validate open documents
199
+ vscode.workspace.textDocuments.forEach(doc => {
200
+ if (doc.languageId === 'javascript') {
201
+ diagnosticsProvider?.validateDocument(doc);
202
+ }
203
+ });
204
+ }
205
+
206
+ export function deactivate() {
207
+ if (diagnosticsProvider) {
208
+ diagnosticsProvider.dispose();
209
+ }
210
+ }
211
+
212
+ // =========================================================================
213
+ // HELPERS
214
+ // =========================================================================
215
+
216
+ interface SystemKnowledge {
217
+ tables: string[];
218
+ tableMetadata: Record<string, any>;
219
+ ruleModules: string[];
220
+ ruleFunctions: Record<string, string[]>;
221
+ maxLookback: number;
222
+ validTypes: string[];
223
+ validCategories: string[];
224
+ }
225
+
226
+ function loadSystemKnowledge(): SystemKnowledge | null {
227
+ try {
228
+ // Try to find the config file
229
+ const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
230
+ if (!workspaceFolder) return null;
231
+
232
+ // Look for bulltrackers.config.js
233
+ const configPaths = [
234
+ path.join(workspaceFolder.uri.fsPath, 'config', 'bulltrackers.config.js'),
235
+ path.join(workspaceFolder.uri.fsPath, 'functions', 'computation-system-v2', 'config', 'bulltrackers.config.js')
236
+ ];
237
+
238
+ for (const configPath of configPaths) {
239
+ if (fs.existsSync(configPath)) {
240
+ // For now, return hardcoded knowledge
241
+ // In production, would parse the config
242
+ return {
243
+ tables: [
244
+ 'portfolio_snapshots',
245
+ 'trade_history_snapshots',
246
+ 'pi_rankings',
247
+ 'social_post_snapshots',
248
+ 'pi_ratings',
249
+ 'pi_page_views',
250
+ 'watchlist_membership',
251
+ 'pi_alert_history',
252
+ 'pi_master_list',
253
+ 'ticker_mappings',
254
+ 'sector_mappings',
255
+ 'asset_prices'
256
+ ],
257
+ tableMetadata: {
258
+ portfolio_snapshots: { dateField: 'date', entityField: 'user_id' },
259
+ pi_rankings: { dateField: 'date', entityField: 'pi_id' },
260
+ trade_history_snapshots: { dateField: 'date', entityField: 'user_id' }
261
+ },
262
+ ruleModules: ['portfolio', 'metrics', 'rankings', 'trades', 'social', 'instruments'],
263
+ ruleFunctions: {
264
+ portfolio: ['extractPositions', 'extractMirrors', 'getInvested', 'getNetProfit', 'extractPortfolioData'],
265
+ metrics: ['calculateSharpeRatio', 'calculateVolatility', 'calculateDrawdown'],
266
+ rankings: ['getRank', 'getPercentile'],
267
+ trades: ['getOpenTrades', 'getClosedTrades', 'getTradeHistory'],
268
+ social: ['getPostCount', 'getEngagement'],
269
+ instruments: ['getTicker', 'getSector', 'getPrice']
270
+ },
271
+ maxLookback: 90,
272
+ validTypes: ['per-entity', 'global'],
273
+ validCategories: ['signed_in_user', 'popular_investor', 'global', 'instrument']
274
+ };
275
+ }
276
+ }
277
+
278
+ return null;
279
+ } catch (e) {
280
+ console.error('Failed to load system knowledge:', e);
281
+ return null;
282
+ }
283
+ }
284
+
285
+ function extractComputationName(text: string): string | null {
286
+ // Look for class definition
287
+ const classMatch = text.match(/class\s+(\w+)\s+extends\s+Computation/);
288
+ if (classMatch) return classMatch[1];
289
+
290
+ // Look for name in getConfig
291
+ const nameMatch = text.match(/name:\s*['"](\w+)['"]/);
292
+ if (nameMatch) return nameMatch[1];
293
+
294
+ return null;
295
+ }
296
+
297
+ function updateStatusBar(editor: vscode.TextEditor | undefined) {
298
+ if (!statusBarItem) return;
299
+
300
+ if (editor && editor.document.languageId === 'javascript' &&
301
+ editor.document.fileName.includes('computation')) {
302
+ const name = extractComputationName(editor.document.getText());
303
+ if (name) {
304
+ statusBarItem.text = `$(graph) ${name}`;
305
+ statusBarItem.tooltip = 'Click to show DAG position';
306
+ statusBarItem.show();
307
+ return;
308
+ }
309
+ }
310
+
311
+ statusBarItem.hide();
312
+ }
313
+
314
+ function generateBoilerplate(name: string, type: string): string {
315
+ return `const { Computation } = require('../framework');
316
+
317
+ class ${name} extends Computation {
318
+ static getConfig() {
319
+ return {
320
+ name: '${name}',
321
+ description: '',
322
+ type: '${type}',
323
+ category: '${type === 'global' ? 'global' : 'signed_in_user'}',
324
+ isHistorical: false,
325
+ requires: {
326
+ 'portfolio_snapshots': {
327
+ lookback: 0,
328
+ mandatory: true,
329
+ fields: ['user_id', 'date', 'portfolio_data']
330
+ }
331
+ },
332
+ storage: {
333
+ bigquery: true,
334
+ firestore: {
335
+ enabled: false,
336
+ path: 'users/{entityId}/${name.toLowerCase()}',
337
+ merge: true
338
+ }
339
+ }
340
+ };
341
+ }
342
+
343
+ async process(data, { entityId, targetDate, rules, getDependency }) {
344
+ const snapshots = data.portfolio_snapshots[entityId] || [];
345
+
346
+ // TODO: Implement computation logic
347
+
348
+ return {
349
+ // result
350
+ };
351
+ }
352
+ }
353
+
354
+ module.exports = ${name};
355
+ `;
356
+ }
357
+
358
+ function getDAGWebviewContent(info: any): string {
359
+ return `<!DOCTYPE html>
360
+ <html>
361
+ <head>
362
+ <style>
363
+ body { font-family: -apple-system, sans-serif; padding: 20px; background: #1e1e1e; color: #d4d4d4; }
364
+ h1 { margin-bottom: 20px; }
365
+ .info { background: #2d2d2d; padding: 15px; border-radius: 8px; margin-bottom: 15px; }
366
+ .label { color: #888; font-size: 0.9em; }
367
+ .value { font-size: 1.1em; margin-top: 4px; }
368
+ .deps { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
369
+ .dep { background: #3d3d3d; padding: 4px 12px; border-radius: 4px; }
370
+ .pass { background: #4a4a9a; color: white; padding: 4px 12px; border-radius: 4px; display: inline-block; }
371
+ </style>
372
+ </head>
373
+ <body>
374
+ <h1>${info.name || 'Unknown'}</h1>
375
+ <div class="info">
376
+ <div class="label">Pass Level</div>
377
+ <div class="value"><span class="pass">Pass ${info.passLevel || '?'}</span> of ${info.totalPasses || '?'}</div>
378
+ </div>
379
+ <div class="info">
380
+ <div class="label">Dependencies</div>
381
+ <div class="deps">
382
+ ${(info.dependencies || []).map((d: string) => `<span class="dep">← ${d}</span>`).join('') || 'None'}
383
+ </div>
384
+ </div>
385
+ <div class="info">
386
+ <div class="label">Dependents</div>
387
+ <div class="deps">
388
+ ${(info.dependents || []).map((d: string) => `<span class="dep">→ ${d}</span>`).join('') || 'None'}
389
+ </div>
390
+ </div>
391
+ <div class="info">
392
+ <div class="label">Type</div>
393
+ <div class="value">${info.config?.type || 'unknown'}</div>
394
+ </div>
395
+ <div class="info">
396
+ <div class="label">Category</div>
397
+ <div class="value">${info.config?.category || 'unknown'}</div>
398
+ </div>
399
+ </body>
400
+ </html>`;
401
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * @fileoverview Code Action Provider
3
+ *
4
+ * Provides quick fixes for:
5
+ * - Unknown table names (suggest similar)
6
+ * - Lookback exceeds max (reduce to max)
7
+ * - Missing required fields (add template)
8
+ */
9
+
10
+ import * as vscode from 'vscode';
11
+
12
+ export class ComputationCodeActionProvider implements vscode.CodeActionProvider {
13
+ private knowledge: SystemKnowledge | null;
14
+
15
+ constructor(knowledge: SystemKnowledge | null) {
16
+ this.knowledge = knowledge;
17
+ }
18
+
19
+ provideCodeActions(
20
+ document: vscode.TextDocument,
21
+ range: vscode.Range | vscode.Selection,
22
+ context: vscode.CodeActionContext,
23
+ token: vscode.CancellationToken
24
+ ): vscode.CodeAction[] | null {
25
+ const actions: vscode.CodeAction[] = [];
26
+
27
+ for (const diagnostic of context.diagnostics) {
28
+ if (diagnostic.source !== 'computation') continue;
29
+
30
+ const message = diagnostic.message;
31
+
32
+ // Unknown table - suggest similar
33
+ if (message.startsWith('Unknown table:')) {
34
+ const tableName = message.match(/Unknown table: (\w+)/)?.[1];
35
+ if (tableName) {
36
+ const suggestions = this.findSimilarTables(tableName);
37
+ for (const suggestion of suggestions) {
38
+ const action = new vscode.CodeAction(
39
+ `Change to '${suggestion}'`,
40
+ vscode.CodeActionKind.QuickFix
41
+ );
42
+ action.edit = new vscode.WorkspaceEdit();
43
+ action.edit.replace(document.uri, diagnostic.range, `'${suggestion}'`);
44
+ action.diagnostics = [diagnostic];
45
+ actions.push(action);
46
+ }
47
+ }
48
+ }
49
+
50
+ // Lookback exceeds max
51
+ if (message.includes('exceeds maximum')) {
52
+ const action = new vscode.CodeAction(
53
+ 'Reduce to maximum (90)',
54
+ vscode.CodeActionKind.QuickFix
55
+ );
56
+ action.edit = new vscode.WorkspaceEdit();
57
+ action.edit.replace(document.uri, diagnostic.range, 'lookback: 90');
58
+ action.diagnostics = [diagnostic];
59
+ actions.push(action);
60
+ }
61
+
62
+ // Missing required field
63
+ if (message.startsWith('Missing required config field:')) {
64
+ const field = message.match(/field: (\w+)/)?.[1];
65
+ if (field) {
66
+ const action = this.createAddFieldAction(document, diagnostic, field);
67
+ if (action) actions.push(action);
68
+ }
69
+ }
70
+ }
71
+
72
+ return actions;
73
+ }
74
+
75
+ private findSimilarTables(name: string): string[] {
76
+ if (!this.knowledge) return [];
77
+
78
+ const suggestions: Array<{ name: string; score: number }> = [];
79
+
80
+ for (const table of this.knowledge.tables) {
81
+ const score = this.similarity(name.toLowerCase(), table.toLowerCase());
82
+ if (score > 0.3) {
83
+ suggestions.push({ name: table, score });
84
+ }
85
+ }
86
+
87
+ return suggestions
88
+ .sort((a, b) => b.score - a.score)
89
+ .slice(0, 3)
90
+ .map(s => s.name);
91
+ }
92
+
93
+ private similarity(a: string, b: string): number {
94
+ // Simple Jaccard similarity on character bigrams
95
+ const bigramsA = new Set<string>();
96
+ const bigramsB = new Set<string>();
97
+
98
+ for (let i = 0; i < a.length - 1; i++) bigramsA.add(a.slice(i, i + 2));
99
+ for (let i = 0; i < b.length - 1; i++) bigramsB.add(b.slice(i, i + 2));
100
+
101
+ let intersection = 0;
102
+ for (const bi of bigramsA) {
103
+ if (bigramsB.has(bi)) intersection++;
104
+ }
105
+
106
+ const union = bigramsA.size + bigramsB.size - intersection;
107
+ return union === 0 ? 0 : intersection / union;
108
+ }
109
+
110
+ private createAddFieldAction(
111
+ document: vscode.TextDocument,
112
+ diagnostic: vscode.Diagnostic,
113
+ field: string
114
+ ): vscode.CodeAction | null {
115
+ const templates: Record<string, string> = {
116
+ name: "name: 'ComputationName',",
117
+ type: "type: 'per-entity',",
118
+ category: "category: 'signed_in_user',"
119
+ };
120
+
121
+ const template = templates[field];
122
+ if (!template) return null;
123
+
124
+ const action = new vscode.CodeAction(
125
+ `Add ${field} field`,
126
+ vscode.CodeActionKind.QuickFix
127
+ );
128
+
129
+ // Find position to insert (after 'return {')
130
+ const text = document.getText();
131
+ const returnIdx = text.indexOf('return {');
132
+ if (returnIdx === -1) return null;
133
+
134
+ const insertPosition = document.positionAt(returnIdx + 'return {'.length);
135
+
136
+ action.edit = new vscode.WorkspaceEdit();
137
+ action.edit.insert(document.uri, insertPosition, `\n ${template}`);
138
+ action.diagnostics = [diagnostic];
139
+
140
+ return action;
141
+ }
142
+ }
143
+
144
+ interface SystemKnowledge {
145
+ tables: string[];
146
+ tableMetadata: Record<string, any>;
147
+ ruleModules: string[];
148
+ ruleFunctions: Record<string, string[]>;
149
+ maxLookback: number;
150
+ validTypes: string[];
151
+ validCategories: string[];
152
+ }