@memberjunction/db-auto-doc 2.117.0 → 2.119.0
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/README.md +803 -165
- package/bin/run.js +7 -0
- package/dist/api/DBAutoDocAPI.d.ts +252 -0
- package/dist/api/DBAutoDocAPI.d.ts.map +1 -0
- package/dist/api/DBAutoDocAPI.js +530 -0
- package/dist/api/DBAutoDocAPI.js.map +1 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +10 -0
- package/dist/api/index.js.map +1 -0
- package/dist/commands/analyze.d.ts +6 -4
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +58 -71
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/export.d.ts +14 -4
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +156 -61
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/generate-queries.d.ts +17 -0
- package/dist/commands/generate-queries.d.ts.map +1 -0
- package/dist/commands/generate-queries.js +182 -0
- package/dist/commands/generate-queries.js.map +1 -0
- package/dist/commands/init.d.ts +3 -4
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +206 -144
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/reset.d.ts +4 -1
- package/dist/commands/reset.d.ts.map +1 -1
- package/dist/commands/reset.js +33 -19
- package/dist/commands/reset.js.map +1 -1
- package/dist/commands/status.d.ts +10 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +66 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/core/AnalysisEngine.d.ts +108 -0
- package/dist/core/AnalysisEngine.d.ts.map +1 -0
- package/dist/core/AnalysisEngine.js +716 -0
- package/dist/core/AnalysisEngine.js.map +1 -0
- package/dist/core/AnalysisOrchestrator.d.ts +41 -0
- package/dist/core/AnalysisOrchestrator.d.ts.map +1 -0
- package/dist/core/AnalysisOrchestrator.js +377 -0
- package/dist/core/AnalysisOrchestrator.js.map +1 -0
- package/dist/core/BackpropagationEngine.d.ts +32 -0
- package/dist/core/BackpropagationEngine.d.ts.map +1 -0
- package/dist/core/BackpropagationEngine.js +121 -0
- package/dist/core/BackpropagationEngine.js.map +1 -0
- package/dist/core/ConvergenceDetector.d.ts +27 -0
- package/dist/core/ConvergenceDetector.d.ts.map +1 -0
- package/dist/core/ConvergenceDetector.js +92 -0
- package/dist/core/ConvergenceDetector.js.map +1 -0
- package/dist/core/GuardrailsManager.d.ts +78 -0
- package/dist/core/GuardrailsManager.d.ts.map +1 -0
- package/dist/core/GuardrailsManager.js +367 -0
- package/dist/core/GuardrailsManager.js.map +1 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +13 -0
- package/dist/core/index.js.map +1 -0
- package/dist/database/Database.d.ts +56 -0
- package/dist/database/Database.d.ts.map +1 -0
- package/dist/database/Database.js +172 -0
- package/dist/database/Database.js.map +1 -0
- package/dist/database/TopologicalSorter.d.ts +25 -0
- package/dist/database/TopologicalSorter.d.ts.map +1 -0
- package/dist/database/TopologicalSorter.js +150 -0
- package/dist/database/TopologicalSorter.js.map +1 -0
- package/dist/database/index.d.ts +6 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +14 -0
- package/dist/database/index.js.map +1 -0
- package/dist/discovery/ColumnStatsCache.d.ts +91 -0
- package/dist/discovery/ColumnStatsCache.d.ts.map +1 -0
- package/dist/discovery/ColumnStatsCache.js +231 -0
- package/dist/discovery/ColumnStatsCache.js.map +1 -0
- package/dist/discovery/DiscoveryEngine.d.ts +100 -0
- package/dist/discovery/DiscoveryEngine.d.ts.map +1 -0
- package/dist/discovery/DiscoveryEngine.js +726 -0
- package/dist/discovery/DiscoveryEngine.js.map +1 -0
- package/dist/discovery/DiscoveryTriggerAnalyzer.d.ts +57 -0
- package/dist/discovery/DiscoveryTriggerAnalyzer.d.ts.map +1 -0
- package/dist/discovery/DiscoveryTriggerAnalyzer.js +186 -0
- package/dist/discovery/DiscoveryTriggerAnalyzer.js.map +1 -0
- package/dist/discovery/FKDetector.d.ts +47 -0
- package/dist/discovery/FKDetector.d.ts.map +1 -0
- package/dist/discovery/FKDetector.js +317 -0
- package/dist/discovery/FKDetector.js.map +1 -0
- package/dist/discovery/LLMDiscoveryValidator.d.ts +64 -0
- package/dist/discovery/LLMDiscoveryValidator.d.ts.map +1 -0
- package/dist/discovery/LLMDiscoveryValidator.js +431 -0
- package/dist/discovery/LLMDiscoveryValidator.js.map +1 -0
- package/dist/discovery/LLMSanityChecker.d.ts +38 -0
- package/dist/discovery/LLMSanityChecker.d.ts.map +1 -0
- package/dist/discovery/LLMSanityChecker.js +156 -0
- package/dist/discovery/LLMSanityChecker.js.map +1 -0
- package/dist/discovery/PKDetector.d.ts +62 -0
- package/dist/discovery/PKDetector.d.ts.map +1 -0
- package/dist/discovery/PKDetector.js +436 -0
- package/dist/discovery/PKDetector.js.map +1 -0
- package/dist/discovery/index.d.ts +9 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +25 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/drivers/BaseAutoDocDriver.d.ts +132 -0
- package/dist/drivers/BaseAutoDocDriver.d.ts.map +1 -0
- package/dist/drivers/BaseAutoDocDriver.js +121 -0
- package/dist/drivers/BaseAutoDocDriver.js.map +1 -0
- package/dist/drivers/MySQLDriver.d.ts +61 -0
- package/dist/drivers/MySQLDriver.d.ts.map +1 -0
- package/dist/drivers/MySQLDriver.js +668 -0
- package/dist/drivers/MySQLDriver.js.map +1 -0
- package/dist/drivers/PostgreSQLDriver.d.ts +65 -0
- package/dist/drivers/PostgreSQLDriver.d.ts.map +1 -0
- package/dist/drivers/PostgreSQLDriver.js +704 -0
- package/dist/drivers/PostgreSQLDriver.js.map +1 -0
- package/dist/drivers/SQLServerDriver.d.ts +61 -0
- package/dist/drivers/SQLServerDriver.d.ts.map +1 -0
- package/dist/drivers/SQLServerDriver.js +667 -0
- package/dist/drivers/SQLServerDriver.js.map +1 -0
- package/dist/generators/CSVGenerator.d.ts +35 -0
- package/dist/generators/CSVGenerator.d.ts.map +1 -0
- package/dist/generators/CSVGenerator.js +154 -0
- package/dist/generators/CSVGenerator.js.map +1 -0
- package/dist/generators/HTMLGenerator.d.ts +29 -0
- package/dist/generators/HTMLGenerator.d.ts.map +1 -0
- package/dist/generators/HTMLGenerator.js +710 -0
- package/dist/generators/HTMLGenerator.js.map +1 -0
- package/dist/generators/MarkdownGenerator.d.ts +27 -0
- package/dist/generators/MarkdownGenerator.d.ts.map +1 -0
- package/dist/generators/MarkdownGenerator.js +361 -0
- package/dist/generators/MarkdownGenerator.js.map +1 -0
- package/dist/generators/MermaidGenerator.d.ts +35 -0
- package/dist/generators/MermaidGenerator.d.ts.map +1 -0
- package/dist/generators/MermaidGenerator.js +321 -0
- package/dist/generators/MermaidGenerator.js.map +1 -0
- package/dist/generators/ReportGenerator.d.ts +22 -0
- package/dist/generators/ReportGenerator.d.ts.map +1 -0
- package/dist/generators/ReportGenerator.js +176 -0
- package/dist/generators/ReportGenerator.js.map +1 -0
- package/dist/generators/SQLGenerator.d.ts +31 -0
- package/dist/generators/SQLGenerator.d.ts.map +1 -0
- package/dist/generators/SQLGenerator.js +168 -0
- package/dist/generators/SQLGenerator.js.map +1 -0
- package/dist/generators/SampleQueryGenerator.d.ts +64 -0
- package/dist/generators/SampleQueryGenerator.d.ts.map +1 -0
- package/dist/generators/SampleQueryGenerator.js +500 -0
- package/dist/generators/SampleQueryGenerator.js.map +1 -0
- package/dist/generators/index.d.ts +10 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +19 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/index.d.ts +11 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -20
- package/dist/index.js.map +1 -1
- package/dist/prompts/PromptEngine.d.ts +65 -0
- package/dist/prompts/PromptEngine.d.ts.map +1 -0
- package/dist/prompts/PromptEngine.js +305 -0
- package/dist/prompts/PromptEngine.js.map +1 -0
- package/dist/prompts/PromptFileLoader.d.ts +21 -0
- package/dist/prompts/PromptFileLoader.d.ts.map +1 -0
- package/dist/prompts/PromptFileLoader.js +74 -0
- package/dist/prompts/PromptFileLoader.js.map +1 -0
- package/dist/prompts/index.d.ts +6 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +11 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/state/IterationTracker.d.ts +64 -0
- package/dist/state/IterationTracker.d.ts.map +1 -0
- package/dist/state/IterationTracker.js +136 -0
- package/dist/state/IterationTracker.js.map +1 -0
- package/dist/state/StateManager.d.ts +79 -0
- package/dist/state/StateManager.d.ts.map +1 -0
- package/dist/state/StateManager.js +348 -0
- package/dist/state/StateManager.js.map +1 -0
- package/dist/state/StateValidator.d.ts +24 -0
- package/dist/state/StateValidator.d.ts.map +1 -0
- package/dist/state/StateValidator.js +147 -0
- package/dist/state/StateValidator.js.map +1 -0
- package/dist/state/index.d.ts +7 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +13 -0
- package/dist/state/index.js.map +1 -0
- package/dist/types/analysis.d.ts +76 -0
- package/dist/types/analysis.d.ts.map +1 -0
- package/dist/types/analysis.js +6 -0
- package/dist/types/analysis.js.map +1 -0
- package/dist/types/config.d.ts +143 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +7 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/discovery.d.ts +277 -0
- package/dist/types/discovery.d.ts.map +1 -0
- package/dist/types/discovery.js +7 -0
- package/dist/types/discovery.js.map +1 -0
- package/dist/types/driver.d.ts +148 -0
- package/dist/types/driver.d.ts.map +1 -0
- package/dist/types/driver.js +7 -0
- package/dist/types/driver.js.map +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +24 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/prompts.d.ts +158 -0
- package/dist/types/prompts.d.ts.map +1 -0
- package/dist/types/prompts.js +6 -0
- package/dist/types/prompts.js.map +1 -0
- package/dist/types/sample-queries.d.ts +172 -0
- package/dist/types/sample-queries.d.ts.map +1 -0
- package/dist/types/sample-queries.js +7 -0
- package/dist/types/sample-queries.js.map +1 -0
- package/dist/types/state.d.ts +291 -0
- package/dist/types/state.d.ts.map +1 -0
- package/dist/types/state.js +7 -0
- package/dist/types/state.js.map +1 -0
- package/dist/utils/config-loader.d.ts +29 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-loader.js +163 -0
- package/dist/utils/config-loader.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +28 -3
- package/dist/ai/simple-ai-client.d.ts +0 -70
- package/dist/ai/simple-ai-client.d.ts.map +0 -1
- package/dist/ai/simple-ai-client.js +0 -181
- package/dist/ai/simple-ai-client.js.map +0 -1
- package/dist/analyzers/analyzer.d.ts +0 -23
- package/dist/analyzers/analyzer.d.ts.map +0 -1
- package/dist/analyzers/analyzer.js +0 -127
- package/dist/analyzers/analyzer.js.map +0 -1
- package/dist/cli-old/cli.d.ts +0 -3
- package/dist/cli-old/cli.d.ts.map +0 -1
- package/dist/cli-old/cli.js +0 -388
- package/dist/cli-old/cli.js.map +0 -1
- package/dist/commands/review.d.ts +0 -11
- package/dist/commands/review.d.ts.map +0 -1
- package/dist/commands/review.js +0 -82
- package/dist/commands/review.js.map +0 -1
- package/dist/database/connection.d.ts +0 -40
- package/dist/database/connection.d.ts.map +0 -1
- package/dist/database/connection.js +0 -136
- package/dist/database/connection.js.map +0 -1
- package/dist/database/introspection.d.ts +0 -59
- package/dist/database/introspection.d.ts.map +0 -1
- package/dist/database/introspection.js +0 -124
- package/dist/database/introspection.js.map +0 -1
- package/dist/generators/markdown-generator.d.ts +0 -8
- package/dist/generators/markdown-generator.d.ts.map +0 -1
- package/dist/generators/markdown-generator.js +0 -106
- package/dist/generators/markdown-generator.js.map +0 -1
- package/dist/generators/sql-generator.d.ts +0 -20
- package/dist/generators/sql-generator.d.ts.map +0 -1
- package/dist/generators/sql-generator.js +0 -83
- package/dist/generators/sql-generator.js.map +0 -1
- package/dist/state/state-manager.d.ts +0 -95
- package/dist/state/state-manager.d.ts.map +0 -1
- package/dist/state/state-manager.js +0 -236
- package/dist/state/state-manager.js.map +0 -1
- package/dist/types/state-file.d.ts +0 -124
- package/dist/types/state-file.d.ts.map +0 -1
- package/dist/types/state-file.js +0 -79
- package/dist/types/state-file.js.map +0 -1
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Main analysis orchestrator
|
|
4
|
+
* Coordinates the entire documentation generation workflow
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.AnalysisEngine = void 0;
|
|
8
|
+
const BackpropagationEngine_js_1 = require("./BackpropagationEngine.js");
|
|
9
|
+
const ConvergenceDetector_js_1 = require("./ConvergenceDetector.js");
|
|
10
|
+
const GuardrailsManager_js_1 = require("./GuardrailsManager.js");
|
|
11
|
+
class AnalysisEngine {
|
|
12
|
+
constructor(config, promptEngine, stateManager, iterationTracker) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.promptEngine = promptEngine;
|
|
15
|
+
this.stateManager = stateManager;
|
|
16
|
+
this.iterationTracker = iterationTracker;
|
|
17
|
+
this.startTime = 0;
|
|
18
|
+
this.backpropagationEngine = new BackpropagationEngine_js_1.BackpropagationEngine(promptEngine, stateManager, iterationTracker, config.analysis.backpropagation.maxDepth);
|
|
19
|
+
this.convergenceDetector = new ConvergenceDetector_js_1.ConvergenceDetector(config.analysis.convergence, stateManager, iterationTracker);
|
|
20
|
+
this.guardrailsManager = new GuardrailsManager_js_1.GuardrailsManager(config.analysis.guardrails);
|
|
21
|
+
// Set up guardrail checking in PromptEngine
|
|
22
|
+
this.promptEngine.setGuardrailCheck(() => {
|
|
23
|
+
if (!this.currentRun) {
|
|
24
|
+
return { canContinue: true };
|
|
25
|
+
}
|
|
26
|
+
const result = this.guardrailsManager.checkGuardrails(this.currentRun);
|
|
27
|
+
this.guardrailsManager.recordEnforcement(this.currentRun, result);
|
|
28
|
+
return result;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Initialize timing for guardrails and set current run
|
|
33
|
+
*/
|
|
34
|
+
startAnalysis(run) {
|
|
35
|
+
this.startTime = Date.now();
|
|
36
|
+
this.currentRun = run;
|
|
37
|
+
this.guardrailsManager.startPhase('analysis');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Process a single dependency level
|
|
41
|
+
*/
|
|
42
|
+
async processLevel(state, run, level, tables) {
|
|
43
|
+
const triggers = [];
|
|
44
|
+
for (const tableNode of tables) {
|
|
45
|
+
const result = await this.analyzeTable(state, run, tableNode, level);
|
|
46
|
+
// Check if guardrail was exceeded during this table's analysis
|
|
47
|
+
if (result.guardrailExceeded) {
|
|
48
|
+
break; // Stop processing this level
|
|
49
|
+
}
|
|
50
|
+
if (result.triggers) {
|
|
51
|
+
triggers.push(...result.triggers);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
run.levelsProcessed = Math.max(run.levelsProcessed, level + 1);
|
|
55
|
+
return triggers;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Analyze a single table
|
|
59
|
+
*/
|
|
60
|
+
async analyzeTable(state, run, tableNode, level) {
|
|
61
|
+
const table = tableNode.tableDefinition;
|
|
62
|
+
if (!table) {
|
|
63
|
+
return {};
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
// Build analysis context
|
|
67
|
+
const context = this.buildTableContext(state, tableNode);
|
|
68
|
+
// Execute analysis prompt
|
|
69
|
+
const result = await this.promptEngine.executePrompt('table-analysis', context, {
|
|
70
|
+
responseFormat: 'JSON',
|
|
71
|
+
temperature: this.config.ai.temperature
|
|
72
|
+
});
|
|
73
|
+
// Check if guardrail was exceeded
|
|
74
|
+
if (result.guardrailExceeded) {
|
|
75
|
+
this.iterationTracker.completeRun(run, false, result.errorMessage || 'Guardrail exceeded');
|
|
76
|
+
return { guardrailExceeded: true };
|
|
77
|
+
}
|
|
78
|
+
if (!result.success || !result.result) {
|
|
79
|
+
this.iterationTracker.addError(run, `Failed to analyze ${tableNode.schema}.${tableNode.table}: ${result.errorMessage}`);
|
|
80
|
+
this.iterationTracker.addLogEntryWithPrompt(run, {
|
|
81
|
+
level,
|
|
82
|
+
schema: tableNode.schema,
|
|
83
|
+
table: tableNode.table,
|
|
84
|
+
action: 'analyze',
|
|
85
|
+
result: 'error',
|
|
86
|
+
message: result.errorMessage
|
|
87
|
+
}, result.promptInput, result.promptOutput);
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
// Track tokens
|
|
91
|
+
this.iterationTracker.addTokenUsage(run, result.tokensUsed, result.cost);
|
|
92
|
+
// Use semantic comparison to check if description materially changed
|
|
93
|
+
const previousDescription = table.description;
|
|
94
|
+
const comparisonResult = await this.compareDescriptions(run, table, result.result, tableNode.schema, tableNode.table);
|
|
95
|
+
const descriptionChanged = comparisonResult.tableMateriallyChanged;
|
|
96
|
+
// Update table description
|
|
97
|
+
this.stateManager.updateTableDescription(table, result.result.tableDescription, result.result.reasoning, result.result.confidence, run.modelUsed, previousDescription ? 'refinement' : 'initial');
|
|
98
|
+
// Update column descriptions
|
|
99
|
+
for (const colDesc of result.result.columnDescriptions || []) {
|
|
100
|
+
const column = table.columns.find(c => c.name === colDesc.columnName);
|
|
101
|
+
if (column) {
|
|
102
|
+
this.stateManager.updateColumnDescription(column, colDesc.description, colDesc.reasoning, run.modelUsed);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Process structured FK insights from LLM and feed back to discovery phase
|
|
106
|
+
if (state.phases.keyDetection && result.result.foreignKeys) {
|
|
107
|
+
this.processFKInsightsFromLLM(state, tableNode.schema, tableNode.table, result.result.foreignKeys);
|
|
108
|
+
}
|
|
109
|
+
// Update inferred business domain
|
|
110
|
+
if (result.result.inferredBusinessDomain) {
|
|
111
|
+
// Could store this in table metadata if needed
|
|
112
|
+
}
|
|
113
|
+
// Log result with prompt I/O and semantic comparison details
|
|
114
|
+
this.iterationTracker.addLogEntryWithPrompt(run, {
|
|
115
|
+
level,
|
|
116
|
+
schema: tableNode.schema,
|
|
117
|
+
table: tableNode.table,
|
|
118
|
+
action: 'analyze',
|
|
119
|
+
result: descriptionChanged ? 'changed' : 'unchanged',
|
|
120
|
+
message: `Confidence: ${result.result.confidence.toFixed(2)}`,
|
|
121
|
+
tokensUsed: result.tokensUsed,
|
|
122
|
+
semanticComparison: comparisonResult
|
|
123
|
+
}, result.promptInput, result.promptOutput);
|
|
124
|
+
// Detect potential insights for parent tables
|
|
125
|
+
const triggers = this.backpropagationEngine.detectParentInsights(table, result.result, tableNode.schema, tableNode.table);
|
|
126
|
+
return { triggers };
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
this.iterationTracker.addError(run, `Exception analyzing ${tableNode.schema}.${tableNode.table}: ${error.message}`);
|
|
130
|
+
this.iterationTracker.addLogEntry(run, {
|
|
131
|
+
level,
|
|
132
|
+
schema: tableNode.schema,
|
|
133
|
+
table: tableNode.table,
|
|
134
|
+
action: 'analyze',
|
|
135
|
+
result: 'error',
|
|
136
|
+
message: error.message
|
|
137
|
+
});
|
|
138
|
+
return {};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Build context for table analysis
|
|
143
|
+
*/
|
|
144
|
+
buildTableContext(state, tableNode) {
|
|
145
|
+
const table = tableNode.tableDefinition;
|
|
146
|
+
// Get parent table descriptions (for context)
|
|
147
|
+
const parentDescriptions = table.dependsOn
|
|
148
|
+
.map(dep => {
|
|
149
|
+
const parentTable = this.stateManager.findTable(state, dep.schema, dep.table);
|
|
150
|
+
if (parentTable && parentTable.description) {
|
|
151
|
+
return {
|
|
152
|
+
schema: dep.schema,
|
|
153
|
+
table: dep.table,
|
|
154
|
+
description: parentTable.description
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
})
|
|
159
|
+
.filter(p => p !== null);
|
|
160
|
+
// Build list of all tables in the database for FK reference validation
|
|
161
|
+
const allTables = [];
|
|
162
|
+
for (const schema of state.schemas) {
|
|
163
|
+
for (const tbl of schema.tables) {
|
|
164
|
+
allTables.push({ schema: schema.name, name: tbl.name });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
schema: tableNode.schema,
|
|
169
|
+
table: tableNode.table,
|
|
170
|
+
rowCount: table.rowCount,
|
|
171
|
+
columns: table.columns.map(col => ({
|
|
172
|
+
name: col.name,
|
|
173
|
+
dataType: col.dataType,
|
|
174
|
+
isNullable: col.isNullable,
|
|
175
|
+
isPrimaryKey: col.isPrimaryKey,
|
|
176
|
+
isForeignKey: col.isForeignKey,
|
|
177
|
+
foreignKeyReferences: col.foreignKeyReferences,
|
|
178
|
+
checkConstraint: col.checkConstraint,
|
|
179
|
+
defaultValue: col.defaultValue,
|
|
180
|
+
possibleValues: col.possibleValues,
|
|
181
|
+
statistics: col.statistics
|
|
182
|
+
})),
|
|
183
|
+
dependsOn: table.dependsOn,
|
|
184
|
+
dependents: table.dependents,
|
|
185
|
+
sampleData: [], // Could add sample rows here if needed
|
|
186
|
+
parentDescriptions: parentDescriptions,
|
|
187
|
+
userNotes: table.userNotes,
|
|
188
|
+
seedContext: state.seedContext,
|
|
189
|
+
allTables
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Compare descriptions using LLM to determine material changes
|
|
194
|
+
*/
|
|
195
|
+
async compareDescriptions(run, table, newResult, schemaName, tableName) {
|
|
196
|
+
// Get previous iteration
|
|
197
|
+
const previousIteration = table.descriptionIterations.length > 0
|
|
198
|
+
? table.descriptionIterations[table.descriptionIterations.length - 1]
|
|
199
|
+
: null;
|
|
200
|
+
// If no previous iteration, it's initial generation - don't waste tokens on comparison
|
|
201
|
+
// Return minimal result with no column changes (they'll all be logged as "initial" anyway)
|
|
202
|
+
if (!previousIteration) {
|
|
203
|
+
return {
|
|
204
|
+
tableMateriallyChanged: true,
|
|
205
|
+
tableChangeReasoning: 'Initial generation (skipped semantic comparison)',
|
|
206
|
+
columnChanges: [] // Don't log individual column changes for initial generation
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
// Build previous column descriptions map
|
|
210
|
+
const previousColumns = table.columns.map(col => ({
|
|
211
|
+
columnName: col.name,
|
|
212
|
+
description: col.description || ''
|
|
213
|
+
}));
|
|
214
|
+
// Build current column descriptions
|
|
215
|
+
const currentColumns = newResult.columnDescriptions.map(col => ({
|
|
216
|
+
columnName: col.columnName,
|
|
217
|
+
description: col.description
|
|
218
|
+
}));
|
|
219
|
+
// Call semantic comparison prompt
|
|
220
|
+
const result = await this.promptEngine.executePrompt('semantic-comparison', {
|
|
221
|
+
schemaName,
|
|
222
|
+
tableName,
|
|
223
|
+
previousIteration: table.descriptionIterations.length,
|
|
224
|
+
currentIteration: table.descriptionIterations.length + 1,
|
|
225
|
+
previousTableDescription: previousIteration.description,
|
|
226
|
+
previousColumns,
|
|
227
|
+
currentTableDescription: newResult.tableDescription,
|
|
228
|
+
currentColumns
|
|
229
|
+
}, {
|
|
230
|
+
responseFormat: 'JSON'
|
|
231
|
+
});
|
|
232
|
+
if (!result.success || !result.result) {
|
|
233
|
+
// If comparison fails, assume changed to be safe
|
|
234
|
+
return {
|
|
235
|
+
tableMateriallyChanged: true,
|
|
236
|
+
tableChangeReasoning: 'Comparison failed - assuming changed for safety',
|
|
237
|
+
columnChanges: newResult.columnDescriptions.map(col => ({
|
|
238
|
+
columnName: col.columnName,
|
|
239
|
+
materiallyChanged: true,
|
|
240
|
+
changeReasoning: 'Comparison failed'
|
|
241
|
+
}))
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
// Track tokens for comparison
|
|
245
|
+
this.iterationTracker.addTokenUsage(run, result.tokensUsed, result.cost);
|
|
246
|
+
return result.result;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Perform dependency-level sanity check
|
|
250
|
+
* Checks consistency across tables at the same dependency level
|
|
251
|
+
*/
|
|
252
|
+
async performDependencyLevelSanityCheck(state, run, level, tables) {
|
|
253
|
+
try {
|
|
254
|
+
const context = {
|
|
255
|
+
dependencyLevel: level,
|
|
256
|
+
tables: tables.map(t => {
|
|
257
|
+
const tableDef = t.tableDefinition;
|
|
258
|
+
return {
|
|
259
|
+
schema: t.schema,
|
|
260
|
+
table: t.table,
|
|
261
|
+
description: tableDef?.description || 'No description yet',
|
|
262
|
+
columns: tableDef?.columns.map(c => ({
|
|
263
|
+
name: c.name,
|
|
264
|
+
description: c.description || 'No description yet'
|
|
265
|
+
})) || [],
|
|
266
|
+
dependsOn: tableDef?.dependsOn || [],
|
|
267
|
+
dependents: tableDef?.dependents || []
|
|
268
|
+
};
|
|
269
|
+
})
|
|
270
|
+
};
|
|
271
|
+
const result = await this.promptEngine.executePrompt('dependency-level-sanity-check', context, {
|
|
272
|
+
responseFormat: 'JSON'
|
|
273
|
+
});
|
|
274
|
+
if (result.success && result.result) {
|
|
275
|
+
// Track this sanity check
|
|
276
|
+
const sanityCheckRecord = {
|
|
277
|
+
timestamp: new Date().toISOString(),
|
|
278
|
+
checkType: 'dependency_level',
|
|
279
|
+
scope: `level ${level}`,
|
|
280
|
+
hasMaterialIssues: result.result.hasMaterialIssues,
|
|
281
|
+
issuesFound: result.result.tableIssues.length,
|
|
282
|
+
tablesAffected: result.result.tableIssues.map(i => i.tableName),
|
|
283
|
+
result: result.result.hasMaterialIssues ? 'issues_corrected' : 'no_issues',
|
|
284
|
+
tokensUsed: result.tokensUsed,
|
|
285
|
+
promptInput: result.promptInput,
|
|
286
|
+
promptOutput: result.promptOutput
|
|
287
|
+
};
|
|
288
|
+
run.sanityChecks.push(sanityCheckRecord);
|
|
289
|
+
run.sanityCheckCount++;
|
|
290
|
+
// Track tokens
|
|
291
|
+
this.iterationTracker.addTokenUsage(run, result.tokensUsed, result.cost);
|
|
292
|
+
// Log issues
|
|
293
|
+
if (result.result.hasMaterialIssues) {
|
|
294
|
+
for (const issue of result.result.tableIssues) {
|
|
295
|
+
this.iterationTracker.addWarning(run, `[Level ${level}] ${issue.severity.toUpperCase()}: ${issue.tableName} - ${issue.description}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return result.result.hasMaterialIssues;
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
this.iterationTracker.addError(run, `Dependency-level sanity check failed for level ${level}: ${error.message}`);
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Perform schema-level sanity check
|
|
309
|
+
* Holistic review after entire schema is analyzed
|
|
310
|
+
*/
|
|
311
|
+
async performSchemaLevelSanityCheck(state, run, schema) {
|
|
312
|
+
try {
|
|
313
|
+
const context = {
|
|
314
|
+
schemaName: schema.name,
|
|
315
|
+
tables: schema.tables.map(t => ({
|
|
316
|
+
name: t.name,
|
|
317
|
+
description: t.description || 'No description yet',
|
|
318
|
+
rowCount: t.rowCount,
|
|
319
|
+
dependencyLevel: t.dependencyLevel,
|
|
320
|
+
columns: t.columns.map(c => ({
|
|
321
|
+
name: c.name,
|
|
322
|
+
dataType: c.dataType,
|
|
323
|
+
description: c.description || 'No description yet'
|
|
324
|
+
})),
|
|
325
|
+
dependsOn: t.dependsOn,
|
|
326
|
+
dependents: t.dependents
|
|
327
|
+
}))
|
|
328
|
+
};
|
|
329
|
+
const result = await this.promptEngine.executePrompt('schema-level-sanity-check', context, {
|
|
330
|
+
responseFormat: 'JSON'
|
|
331
|
+
});
|
|
332
|
+
if (result.success && result.result) {
|
|
333
|
+
// Update schema description if provided
|
|
334
|
+
if (result.result.schemaLevelIssues.some(i => i.suggestedSchemaDescription)) {
|
|
335
|
+
const suggested = result.result.schemaLevelIssues.find(i => i.suggestedSchemaDescription);
|
|
336
|
+
if (suggested?.suggestedSchemaDescription) {
|
|
337
|
+
this.stateManager.updateSchemaDescription(schema, suggested.suggestedSchemaDescription, 'Generated from schema-level sanity check', run.modelUsed);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// Track this sanity check
|
|
341
|
+
const sanityCheckRecord = {
|
|
342
|
+
timestamp: new Date().toISOString(),
|
|
343
|
+
checkType: 'schema_level',
|
|
344
|
+
scope: `${schema.name} schema`,
|
|
345
|
+
hasMaterialIssues: result.result.hasMaterialIssues,
|
|
346
|
+
issuesFound: result.result.schemaLevelIssues.length + result.result.tableIssues.length,
|
|
347
|
+
tablesAffected: [
|
|
348
|
+
...result.result.tableIssues.map(i => i.tableName),
|
|
349
|
+
...result.result.schemaLevelIssues.flatMap(i => i.affectedTables)
|
|
350
|
+
],
|
|
351
|
+
result: result.result.hasMaterialIssues ? 'issues_corrected' : 'no_issues',
|
|
352
|
+
tokensUsed: result.tokensUsed,
|
|
353
|
+
promptInput: result.promptInput,
|
|
354
|
+
promptOutput: result.promptOutput
|
|
355
|
+
};
|
|
356
|
+
run.sanityChecks.push(sanityCheckRecord);
|
|
357
|
+
run.sanityCheckCount++;
|
|
358
|
+
// Track tokens
|
|
359
|
+
this.iterationTracker.addTokenUsage(run, result.tokensUsed, result.cost);
|
|
360
|
+
// Log issues
|
|
361
|
+
if (result.result.hasMaterialIssues) {
|
|
362
|
+
for (const issue of result.result.schemaLevelIssues) {
|
|
363
|
+
this.iterationTracker.addWarning(run, `[Schema ${schema.name}] ${issue.severity.toUpperCase()}: ${issue.issueType} - ${issue.description}`);
|
|
364
|
+
}
|
|
365
|
+
for (const issue of result.result.tableIssues) {
|
|
366
|
+
this.iterationTracker.addWarning(run, `[Schema ${schema.name}] ${issue.severity.toUpperCase()}: ${issue.tableName} - ${issue.description}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return result.result.hasMaterialIssues;
|
|
370
|
+
}
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
this.iterationTracker.addError(run, `Schema-level sanity check failed for ${schema.name}: ${error.message}`);
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Perform cross-schema sanity check
|
|
380
|
+
* Validates consistency across all schemas
|
|
381
|
+
*/
|
|
382
|
+
async performCrossSchemaSanityCheck(state, run) {
|
|
383
|
+
if (state.schemas.length <= 1) {
|
|
384
|
+
return false; // No need for cross-schema check
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
const context = {
|
|
388
|
+
schemas: state.schemas.map(s => ({
|
|
389
|
+
schemaName: s.name,
|
|
390
|
+
description: s.description || 'No description yet',
|
|
391
|
+
tableCount: s.tables.length,
|
|
392
|
+
tables: s.tables.map(t => ({
|
|
393
|
+
name: t.name,
|
|
394
|
+
description: t.description || 'No description yet',
|
|
395
|
+
rowCount: t.rowCount
|
|
396
|
+
}))
|
|
397
|
+
}))
|
|
398
|
+
};
|
|
399
|
+
const result = await this.promptEngine.executePrompt('cross-schema-sanity-check', context, {
|
|
400
|
+
responseFormat: 'JSON'
|
|
401
|
+
});
|
|
402
|
+
if (result.success && result.result) {
|
|
403
|
+
// Track this sanity check
|
|
404
|
+
const sanityCheckRecord = {
|
|
405
|
+
timestamp: new Date().toISOString(),
|
|
406
|
+
checkType: 'cross_schema',
|
|
407
|
+
scope: 'all schemas',
|
|
408
|
+
hasMaterialIssues: result.result.hasMaterialIssues,
|
|
409
|
+
issuesFound: result.result.crossSchemaIssues.length + result.result.schemaIssues.length,
|
|
410
|
+
tablesAffected: result.result.crossSchemaIssues.flatMap(i => i.affectedTables.map(t => `${t.schema}.${t.table}`)),
|
|
411
|
+
result: result.result.hasMaterialIssues ? 'issues_corrected' : 'no_issues',
|
|
412
|
+
tokensUsed: result.tokensUsed,
|
|
413
|
+
promptInput: result.promptInput,
|
|
414
|
+
promptOutput: result.promptOutput
|
|
415
|
+
};
|
|
416
|
+
run.sanityChecks.push(sanityCheckRecord);
|
|
417
|
+
run.sanityCheckCount++;
|
|
418
|
+
// Track tokens
|
|
419
|
+
this.iterationTracker.addTokenUsage(run, result.tokensUsed, result.cost);
|
|
420
|
+
// Log issues
|
|
421
|
+
if (result.result.hasMaterialIssues) {
|
|
422
|
+
for (const issue of result.result.crossSchemaIssues) {
|
|
423
|
+
this.iterationTracker.addWarning(run, `[Cross-Schema] ${issue.severity.toUpperCase()}: ${issue.issueType} - ${issue.description}`);
|
|
424
|
+
}
|
|
425
|
+
for (const issue of result.result.schemaIssues) {
|
|
426
|
+
this.iterationTracker.addWarning(run, `[Schema ${issue.schemaName}] ${issue.issueType} - ${issue.description}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return result.result.hasMaterialIssues;
|
|
430
|
+
}
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
this.iterationTracker.addError(run, `Cross-schema sanity check failed: ${error.message}`);
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Check convergence
|
|
440
|
+
*/
|
|
441
|
+
checkConvergence(state, run) {
|
|
442
|
+
const result = this.convergenceDetector.hasConverged(state, run);
|
|
443
|
+
if (result.converged) {
|
|
444
|
+
this.iterationTracker.completeRun(run, true, result.reason);
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Execute backpropagation
|
|
451
|
+
*/
|
|
452
|
+
async executeBackpropagation(state, run, triggers) {
|
|
453
|
+
if (!this.config.analysis.backpropagation.enabled) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (triggers.length === 0) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
await this.backpropagationEngine.execute(state, run, triggers);
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Extract FK insights from column descriptions and create feedback to discovery phase
|
|
463
|
+
*
|
|
464
|
+
* **DEPRECATED**: This method previously used brittle regex heuristics to parse natural language
|
|
465
|
+
* descriptions. Per architectural decision, we should use LLM for language understanding, not regex.
|
|
466
|
+
*
|
|
467
|
+
* **TODO**: Replace with structured LLM output approach:
|
|
468
|
+
* 1. Update table-analysis prompt to include a "foreignKeys" array in JSON response:
|
|
469
|
+
* {
|
|
470
|
+
* "tableDescription": "...",
|
|
471
|
+
* "columnDescriptions": [...],
|
|
472
|
+
* "foreignKeys": [
|
|
473
|
+
* { "column": "prd_id", "referencesTable": "inv.prd", "referencesColumn": "prd_id" }
|
|
474
|
+
* ]
|
|
475
|
+
* }
|
|
476
|
+
* 2. Process the structured foreignKeys array directly instead of parsing descriptions
|
|
477
|
+
* 3. Use deterministic code only for statistics calculation, LLM for reasoning
|
|
478
|
+
*
|
|
479
|
+
* For now, this method is disabled to prevent brittle regex-based FK detection.
|
|
480
|
+
*/
|
|
481
|
+
extractAndFeedbackFKInsights(state, schemaName, tableName, columnDescriptions) {
|
|
482
|
+
// Method disabled - awaiting structured LLM output implementation
|
|
483
|
+
console.log(`[AnalysisEngine] extractAndFeedbackFKInsights disabled - awaiting structured FK output from LLM`);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Process structured FK insights from LLM and create feedback to discovery phase
|
|
488
|
+
*
|
|
489
|
+
* Uses the foreignKeys array from table-analysis prompt response instead of brittle regex parsing.
|
|
490
|
+
* Per architectural decision: use LLM for language understanding, deterministic code for processing.
|
|
491
|
+
*/
|
|
492
|
+
processFKInsightsFromLLM(state, schemaName, tableName, foreignKeys) {
|
|
493
|
+
const discoveryPhase = state.phases.keyDetection;
|
|
494
|
+
if (!discoveryPhase || !foreignKeys || foreignKeys.length === 0)
|
|
495
|
+
return;
|
|
496
|
+
console.log(`[AnalysisEngine] Processing ${foreignKeys.length} structured FK insights from LLM for ${schemaName}.${tableName}`);
|
|
497
|
+
for (const fk of foreignKeys) {
|
|
498
|
+
const { columnName, referencesSchema, referencesTable, referencesColumn, confidence } = fk;
|
|
499
|
+
// Create feedback for this FK
|
|
500
|
+
const feedback = {
|
|
501
|
+
type: 'new_relationship',
|
|
502
|
+
evidence: `LLM-identified FK: ${columnName} -> ${referencesSchema}.${referencesTable}.${referencesColumn} (confidence: ${confidence})`,
|
|
503
|
+
tableName,
|
|
504
|
+
columnName,
|
|
505
|
+
affectedCandidates: [],
|
|
506
|
+
recommendation: 'add_new',
|
|
507
|
+
newRelationship: {
|
|
508
|
+
targetTable: `${referencesSchema}.${referencesTable}`,
|
|
509
|
+
targetColumn: referencesColumn
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
// Check if this column was incorrectly marked as a PK - reject it unless it's a surrogate key
|
|
513
|
+
const falsePK = discoveryPhase.discovered.primaryKeys.find(pk => pk.schemaName === schemaName &&
|
|
514
|
+
pk.tableName === tableName &&
|
|
515
|
+
pk.columnNames.includes(columnName));
|
|
516
|
+
if (falsePK) {
|
|
517
|
+
const columnLower = columnName.toLowerCase();
|
|
518
|
+
const tableLower = tableName.toLowerCase();
|
|
519
|
+
const isSurrogateKey = columnLower === `${tableLower}_id` ||
|
|
520
|
+
columnLower === tableLower + 'id' ||
|
|
521
|
+
columnLower === 'id';
|
|
522
|
+
if (!isSurrogateKey) {
|
|
523
|
+
falsePK.status = 'rejected';
|
|
524
|
+
feedback.affectedCandidates.push(`PK:${schemaName}.${tableName}.${columnName}`);
|
|
525
|
+
const column = this.findColumnInState(state, schemaName, tableName, columnName);
|
|
526
|
+
if (column)
|
|
527
|
+
column.isPrimaryKey = false;
|
|
528
|
+
console.log(`[AnalysisEngine] FK from LLM: ${schemaName}.${tableName}.${columnName} -> ${referencesSchema}.${referencesTable}, rejecting as PK`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
// Check if we already have this FK - boost confidence
|
|
532
|
+
const existingFK = discoveryPhase.discovered.foreignKeys.find(fk => fk.schemaName === schemaName &&
|
|
533
|
+
fk.sourceTable === tableName &&
|
|
534
|
+
fk.sourceColumn === columnName);
|
|
535
|
+
if (existingFK && existingFK.status === 'candidate') {
|
|
536
|
+
existingFK.validatedByLLM = true;
|
|
537
|
+
existingFK.status = 'confirmed';
|
|
538
|
+
existingFK.confidence = Math.min(existingFK.confidence + 20, 100);
|
|
539
|
+
feedback.type = 'confidence_change';
|
|
540
|
+
feedback.newConfidence = existingFK.confidence;
|
|
541
|
+
feedback.affectedCandidates.push(`FK:${schemaName}.${tableName}.${columnName}`);
|
|
542
|
+
const column = this.findColumnInState(state, schemaName, tableName, columnName);
|
|
543
|
+
if (column) {
|
|
544
|
+
column.isForeignKey = true;
|
|
545
|
+
column.foreignKeyReferences = {
|
|
546
|
+
schema: referencesSchema,
|
|
547
|
+
table: referencesTable,
|
|
548
|
+
column: referencesColumn,
|
|
549
|
+
referencedColumn: referencesColumn
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
// Update table-level dependsOn and dependents arrays for ERD generation
|
|
553
|
+
this.updateTableDependencies(state, schemaName, tableName, referencesSchema, referencesTable, columnName, referencesColumn);
|
|
554
|
+
console.log(`[AnalysisEngine] Confirmed FK: ${schemaName}.${tableName}.${columnName} -> ${referencesSchema}.${referencesTable}.${referencesColumn}, confidence: ${existingFK.confidence}`);
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
// Create new FK from LLM insight
|
|
558
|
+
const newFK = {
|
|
559
|
+
schemaName,
|
|
560
|
+
sourceTable: tableName,
|
|
561
|
+
sourceColumn: columnName,
|
|
562
|
+
targetSchema: referencesSchema,
|
|
563
|
+
targetTable: referencesTable,
|
|
564
|
+
targetColumn: referencesColumn,
|
|
565
|
+
confidence: Math.round(confidence * 100),
|
|
566
|
+
evidence: {
|
|
567
|
+
namingMatch: 0.9,
|
|
568
|
+
valueOverlap: 0,
|
|
569
|
+
cardinalityRatio: 0,
|
|
570
|
+
dataTypeMatch: true,
|
|
571
|
+
nullPercentage: 0,
|
|
572
|
+
sampleSize: 0,
|
|
573
|
+
orphanCount: 0,
|
|
574
|
+
warnings: ['Created from structured LLM output']
|
|
575
|
+
},
|
|
576
|
+
discoveredInIteration: 1,
|
|
577
|
+
validatedByLLM: true,
|
|
578
|
+
status: 'confirmed'
|
|
579
|
+
};
|
|
580
|
+
discoveryPhase.discovered.foreignKeys.push(newFK);
|
|
581
|
+
feedback.newConfidence = newFK.confidence;
|
|
582
|
+
feedback.affectedCandidates.push(`FK:${schemaName}.${tableName}.${columnName}`);
|
|
583
|
+
const column = this.findColumnInState(state, schemaName, tableName, columnName);
|
|
584
|
+
if (column) {
|
|
585
|
+
column.isForeignKey = true;
|
|
586
|
+
column.foreignKeyReferences = {
|
|
587
|
+
schema: referencesSchema,
|
|
588
|
+
table: referencesTable,
|
|
589
|
+
column: referencesColumn,
|
|
590
|
+
referencedColumn: referencesColumn
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
// Update table-level dependsOn and dependents arrays for ERD generation
|
|
594
|
+
this.updateTableDependencies(state, schemaName, tableName, referencesSchema, referencesTable, columnName, referencesColumn);
|
|
595
|
+
console.log(`[AnalysisEngine] Created FK from LLM: ${schemaName}.${tableName}.${columnName} -> ${referencesSchema}.${referencesTable}.${referencesColumn}`);
|
|
596
|
+
}
|
|
597
|
+
discoveryPhase.feedbackFromAnalysis.push(feedback);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Find a column in the state schemas
|
|
602
|
+
*/
|
|
603
|
+
findColumnInState(state, schemaName, tableName, columnName) {
|
|
604
|
+
const schema = state.schemas.find(s => s.name === schemaName);
|
|
605
|
+
if (!schema)
|
|
606
|
+
return null;
|
|
607
|
+
const table = schema.tables.find(t => t.name === tableName);
|
|
608
|
+
if (!table)
|
|
609
|
+
return null;
|
|
610
|
+
return table.columns.find(c => c.name === columnName) || null;
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Update table-level dependsOn and dependents arrays when creating FK relationships
|
|
614
|
+
* This is required for ERD diagram generation
|
|
615
|
+
*/
|
|
616
|
+
updateTableDependencies(state, sourceSchemaName, sourceTableName, targetSchemaName, targetTableName, sourceColumnName, targetColumnName) {
|
|
617
|
+
// Find source table and add to its dependsOn array
|
|
618
|
+
const sourceSchema = state.schemas.find(s => s.name === sourceSchemaName);
|
|
619
|
+
if (sourceSchema) {
|
|
620
|
+
const sourceTable = sourceSchema.tables.find(t => t.name === sourceTableName);
|
|
621
|
+
if (sourceTable) {
|
|
622
|
+
// Check if this dependency already exists
|
|
623
|
+
const existingDep = sourceTable.dependsOn.find(dep => dep.schema === targetSchemaName &&
|
|
624
|
+
dep.table === targetTableName &&
|
|
625
|
+
dep.column === sourceColumnName);
|
|
626
|
+
if (!existingDep) {
|
|
627
|
+
sourceTable.dependsOn.push({
|
|
628
|
+
schema: targetSchemaName,
|
|
629
|
+
table: targetTableName,
|
|
630
|
+
column: sourceColumnName,
|
|
631
|
+
referencedColumn: targetColumnName
|
|
632
|
+
});
|
|
633
|
+
console.log(`[AnalysisEngine] Updated dependsOn for ${sourceSchemaName}.${sourceTableName} -> ${targetSchemaName}.${targetTableName}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
// Find target table and add to its dependents array
|
|
638
|
+
const targetSchema = state.schemas.find(s => s.name === targetSchemaName);
|
|
639
|
+
if (targetSchema) {
|
|
640
|
+
const targetTable = targetSchema.tables.find(t => t.name === targetTableName);
|
|
641
|
+
if (targetTable) {
|
|
642
|
+
// Check if this dependent already exists
|
|
643
|
+
const existingDep = targetTable.dependents.find(dep => dep.schema === sourceSchemaName &&
|
|
644
|
+
dep.table === sourceTableName &&
|
|
645
|
+
dep.column === sourceColumnName);
|
|
646
|
+
if (!existingDep) {
|
|
647
|
+
targetTable.dependents.push({
|
|
648
|
+
schema: sourceSchemaName,
|
|
649
|
+
table: sourceTableName,
|
|
650
|
+
column: sourceColumnName,
|
|
651
|
+
referencedColumn: targetColumnName
|
|
652
|
+
});
|
|
653
|
+
console.log(`[AnalysisEngine] Updated dependents for ${targetSchemaName}.${targetTableName} <- ${sourceSchemaName}.${sourceTableName}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Resolve target table from LLM hint (e.g., "product", "warehouse", "supplier")
|
|
660
|
+
* Returns the best matching table and its likely PK column
|
|
661
|
+
*/
|
|
662
|
+
resolveTargetTable(state, tableHint, currentSchema) {
|
|
663
|
+
const hint = tableHint.toLowerCase().trim();
|
|
664
|
+
// Search all schemas for matching table names
|
|
665
|
+
for (const schema of state.schemas) {
|
|
666
|
+
for (const table of schema.tables) {
|
|
667
|
+
const tableLower = table.name.toLowerCase();
|
|
668
|
+
// Direct match
|
|
669
|
+
if (tableLower === hint || tableLower === hint + 's' || tableLower + 's' === hint) {
|
|
670
|
+
// Find PK column (prefer columns ending in _id or named id)
|
|
671
|
+
const pkColumn = table.columns.find(c => c.isPrimaryKey);
|
|
672
|
+
if (pkColumn) {
|
|
673
|
+
return {
|
|
674
|
+
schema: schema.name,
|
|
675
|
+
table: table.name,
|
|
676
|
+
column: pkColumn.name
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
// Fallback: look for column ending in _id or just 'id'
|
|
680
|
+
const idColumn = table.columns.find(c => c.name.toLowerCase() === 'id' ||
|
|
681
|
+
c.name.toLowerCase() === `${tableLower}_id` ||
|
|
682
|
+
c.name.toLowerCase().endsWith('_id'));
|
|
683
|
+
if (idColumn) {
|
|
684
|
+
return {
|
|
685
|
+
schema: schema.name,
|
|
686
|
+
table: table.name,
|
|
687
|
+
column: idColumn.name
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
// Fallback: first column
|
|
691
|
+
if (table.columns.length > 0) {
|
|
692
|
+
return {
|
|
693
|
+
schema: schema.name,
|
|
694
|
+
table: table.name,
|
|
695
|
+
column: table.columns[0].name
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
// Partial match (e.g., "product" matches "prd", "products")
|
|
700
|
+
if (tableLower.includes(hint) || hint.includes(tableLower)) {
|
|
701
|
+
const pkColumn = table.columns.find(c => c.isPrimaryKey);
|
|
702
|
+
if (pkColumn) {
|
|
703
|
+
return {
|
|
704
|
+
schema: schema.name,
|
|
705
|
+
table: table.name,
|
|
706
|
+
column: pkColumn.name
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
exports.AnalysisEngine = AnalysisEngine;
|
|
716
|
+
//# sourceMappingURL=AnalysisEngine.js.map
|