dbagent-cli 0.9.3__tar.gz → 0.9.5__tar.gz
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.
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/PKG-INFO +1 -1
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/__init__.py +1 -1
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/generator.py +6 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/cli.py +65 -21
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/connectors/base.py +27 -3
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/ground_truth.py +31 -3
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent_cli.egg-info/PKG-INFO +1 -1
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/pyproject.toml +1 -1
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/README.md +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/confidence.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/conversation.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/doctor.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/error_classifier.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/pipeline.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/recommender.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/agent/validator.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/config.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/connectors/factory.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/connectors/mongo.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/connectors/relational.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/__init__.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/cache.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/changeset.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/cost.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/recovery.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/sandbox.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/execution/streaming.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/anthropic_provider.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/auto_setup.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/base.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/deepseek_provider.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/factory.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/gemini_provider.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/groq_provider.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/mistral_provider.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/mock_provider.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/ollama_provider.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/openai_provider.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/llm/openrouter_provider.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/cache.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/formatter.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/graph.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/models.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/retriever.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/schema/selector.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/security/__init__.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/security/audit.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/security/environment.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/security/masking.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/security/modes.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/security/rbac.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/semantic/__init__.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/semantic/dictionary.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/semantic/knowledge.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/semantic/templates.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/sql/__init__.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/sql/optimizer.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/sql/validator.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/ui/chat_screen.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/ui/console.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/ui/grid_window.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/ui/viewer.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent/ui/visualizer.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent_cli.egg-info/SOURCES.txt +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent_cli.egg-info/dependency_links.txt +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent_cli.egg-info/entry_points.txt +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent_cli.egg-info/requires.txt +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/dbagent_cli.egg-info/top_level.txt +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/setup.cfg +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_cache.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_cli.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_connectors.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_direct_sql.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_doctor_and_viz.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_generator.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_graph.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_knowledge.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_providers.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_recovery.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_safety.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_sandbox.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_schema.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_security.py +0 -0
- {dbagent_cli-0.9.3 → dbagent_cli-0.9.5}/tests/test_semantic.py +0 -0
|
@@ -166,11 +166,17 @@ class ScriptGenerator:
|
|
|
166
166
|
"related tables", "related tebales", "table mappings", "related tables mappings",
|
|
167
167
|
"related tebales mappings", "not correct", "not right", "check all the tables",
|
|
168
168
|
"mappings also", "find all mappings", "check links", "check mappings",
|
|
169
|
+
"is fk_", "are fk_", "is pk_", "are pk_", "difference between", "compare",
|
|
170
|
+
"tell me", "check it and tell me", "check and tell me", "same or different",
|
|
169
171
|
]
|
|
170
172
|
for kw in analysis_keywords:
|
|
171
173
|
if kw in prompt_lower:
|
|
172
174
|
return "analysis"
|
|
173
175
|
|
|
176
|
+
# Comparative analysis questions (e.g. "is fk_site_group_id and fk_group_id same", "are table_a and table_b related")
|
|
177
|
+
if re.search(r'\b(?:is|are)\b.*\b(?:same|different|difference|related|equal|linked)\b', prompt_lower):
|
|
178
|
+
return "analysis"
|
|
179
|
+
|
|
174
180
|
# Contextual clarification for analysis (e.g. "i mean ivis_scope", "i mean table_x")
|
|
175
181
|
if re.search(r'\bi mean\b', prompt_lower) and not re.search(r'\b(insert|update|delete|drop)\b', prompt_lower):
|
|
176
182
|
return "analysis"
|
|
@@ -98,10 +98,13 @@ audit_logger = AuditLogger()
|
|
|
98
98
|
# Dynamic Animated Spinner with Rotating Messages
|
|
99
99
|
# ---------------------------------------------------------------------------
|
|
100
100
|
|
|
101
|
+
_active_spinner: Optional["DynamicSpinner"] = None
|
|
102
|
+
|
|
103
|
+
|
|
101
104
|
class DynamicSpinner:
|
|
102
105
|
"""
|
|
103
106
|
Context manager that shows an animated spinner with rotating contextual messages.
|
|
104
|
-
Messages cycle every ~1.
|
|
107
|
+
Messages cycle every ~1.2s so the user always sees progress.
|
|
105
108
|
Displays elapsed time suffix like (2.3s).
|
|
106
109
|
"""
|
|
107
110
|
|
|
@@ -193,7 +196,7 @@ class DynamicSpinner:
|
|
|
193
196
|
]
|
|
194
197
|
return cls(messages=messages, interval=1.2)
|
|
195
198
|
|
|
196
|
-
def __init__(self, messages: Optional[List[str]] = None, interval: float = 1.
|
|
199
|
+
def __init__(self, messages: Optional[List[str]] = None, interval: float = 1.2):
|
|
197
200
|
self._messages = messages or self.DEFAULT_MESSAGES
|
|
198
201
|
self._interval = interval
|
|
199
202
|
self._stop_event = threading.Event()
|
|
@@ -202,6 +205,8 @@ class DynamicSpinner:
|
|
|
202
205
|
self._status = None
|
|
203
206
|
|
|
204
207
|
def __enter__(self):
|
|
208
|
+
global _active_spinner
|
|
209
|
+
_active_spinner = self
|
|
205
210
|
self._start_time = time.time()
|
|
206
211
|
self._stop_event.clear()
|
|
207
212
|
self._status = console.status(f"[bold cyan]{self._messages[0]}[/bold cyan]")
|
|
@@ -211,11 +216,36 @@ class DynamicSpinner:
|
|
|
211
216
|
return self
|
|
212
217
|
|
|
213
218
|
def __exit__(self, *args):
|
|
219
|
+
global _active_spinner
|
|
220
|
+
_active_spinner = None
|
|
214
221
|
self._stop_event.set()
|
|
215
222
|
if self._thread:
|
|
216
|
-
self._thread.join(timeout=
|
|
223
|
+
self._thread.join(timeout=1)
|
|
224
|
+
if self._status:
|
|
225
|
+
try:
|
|
226
|
+
self._status.__exit__(*args)
|
|
227
|
+
except Exception:
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
def pause(self):
|
|
231
|
+
"""Temporarily pause the spinner so interactive prompts don't clash."""
|
|
232
|
+
self._stop_event.set()
|
|
217
233
|
if self._status:
|
|
218
|
-
|
|
234
|
+
try:
|
|
235
|
+
self._status.stop()
|
|
236
|
+
except Exception:
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
def resume(self):
|
|
240
|
+
"""Resume the spinner after interactive prompt finishes."""
|
|
241
|
+
self._stop_event.clear()
|
|
242
|
+
if self._status:
|
|
243
|
+
try:
|
|
244
|
+
self._status.start()
|
|
245
|
+
except Exception:
|
|
246
|
+
pass
|
|
247
|
+
self._thread = threading.Thread(target=self._rotate, daemon=True)
|
|
248
|
+
self._thread.start()
|
|
219
249
|
|
|
220
250
|
@property
|
|
221
251
|
def elapsed_ms(self) -> float:
|
|
@@ -265,28 +295,42 @@ def resolve_db_url(db_arg: Optional[str] = None) -> str:
|
|
|
265
295
|
|
|
266
296
|
def _confirm_callback(sql: str, warnings: List[str]) -> bool:
|
|
267
297
|
"""Prompt user to confirm a write or DDL operation."""
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
298
|
+
global _active_spinner
|
|
299
|
+
if _active_spinner:
|
|
300
|
+
_active_spinner.pause()
|
|
301
|
+
try:
|
|
302
|
+
console.print("")
|
|
303
|
+
print_code(sql, "sql", "Action Requires Confirmation")
|
|
304
|
+
for w in warnings:
|
|
305
|
+
print_warning(w)
|
|
306
|
+
return Confirm.ask("Execute this statement on the database?", default=False)
|
|
307
|
+
finally:
|
|
308
|
+
if _active_spinner:
|
|
309
|
+
_active_spinner.resume()
|
|
273
310
|
|
|
274
311
|
|
|
275
312
|
def _choice_callback(prompt_text: str, choices: List[str]) -> Optional[str]:
|
|
276
313
|
"""Prompt user to disambiguate table choices."""
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
choice = Prompt.ask("Select number or table name", default="1")
|
|
314
|
+
global _active_spinner
|
|
315
|
+
if _active_spinner:
|
|
316
|
+
_active_spinner.pause()
|
|
281
317
|
try:
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
318
|
+
console.print(f"\n[bold yellow]?[/bold yellow] {prompt_text}")
|
|
319
|
+
for i, c in enumerate(choices, 1):
|
|
320
|
+
console.print(f" [{i}] {c}")
|
|
321
|
+
choice = Prompt.ask("Select number or table name", default="1")
|
|
322
|
+
try:
|
|
323
|
+
idx = int(choice) - 1
|
|
324
|
+
if 0 <= idx < len(choices):
|
|
325
|
+
return choices[idx]
|
|
326
|
+
except ValueError:
|
|
327
|
+
pass
|
|
328
|
+
if choice in choices:
|
|
329
|
+
return choice
|
|
330
|
+
return choices[0]
|
|
331
|
+
finally:
|
|
332
|
+
if _active_spinner:
|
|
333
|
+
_active_spinner.resume()
|
|
290
334
|
|
|
291
335
|
|
|
292
336
|
def _display_pipeline_result(result: PipelineResult, show_sql: bool = True) -> None:
|
|
@@ -53,7 +53,29 @@ class BaseConnector(ABC):
|
|
|
53
53
|
exact = []
|
|
54
54
|
fuzzy = []
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
stop_words = {
|
|
57
|
+
"check", "tell", "same", "show", "list", "find", "get", "is", "are", "does", "will", "can", "what",
|
|
58
|
+
"which", "how", "why", "where", "with", "and", "from", "that", "this", "these", "those", "have",
|
|
59
|
+
"has", "had", "not", "all", "any", "few", "some", "many", "each", "every", "table", "tables",
|
|
60
|
+
"column", "columns", "field", "fields", "row", "rows", "record", "records", "data", "info",
|
|
61
|
+
"information", "name", "names", "type", "types", "key", "keys", "id", "ids", "status", "true",
|
|
62
|
+
"false", "null", "seriously", "please", "mean", "like", "about", "between", "among", "into",
|
|
63
|
+
"onto", "over", "under", "before", "after", "during", "since", "until", "across", "along",
|
|
64
|
+
"throughout", "outside", "inside", "above", "below", "behind", "beyond", "plus", "except",
|
|
65
|
+
"but", "against", "towards", "upon", "within", "without", "give", "also", "explain", "look",
|
|
66
|
+
"know", "make", "take", "help", "view", "count", "done", "good", "nice", "okay", "fine"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
raw_tokens = set(re.findall(r'\b[a-zA-Z0-9_]+\b', prompt_lower))
|
|
70
|
+
tokens = set(raw_tokens)
|
|
71
|
+
|
|
72
|
+
# Extract entity stems from foreign key column tokens (e.g. fk_site_group_id -> site_group, fk_group_id -> group)
|
|
73
|
+
for tok in raw_tokens:
|
|
74
|
+
if tok.startswith("fk_") or tok.startswith("pk_"):
|
|
75
|
+
entity = re.sub(r'^(?:fk_|pk_)', '', tok)
|
|
76
|
+
entity = re.sub(r'_(?:id|code|key|no|num)$', '', entity)
|
|
77
|
+
if entity and len(entity) >= 3 and entity not in stop_words:
|
|
78
|
+
tokens.add(entity)
|
|
57
79
|
|
|
58
80
|
for t in all_tables:
|
|
59
81
|
t_lower = t.lower()
|
|
@@ -64,7 +86,7 @@ class BaseConnector(ABC):
|
|
|
64
86
|
|
|
65
87
|
# 2. Match without common prefixes (e.g. "ivis_scope" -> "scope", "scopes")
|
|
66
88
|
unprefixed = re.sub(r'^(?:ivis_|tbl_|table_|app_)', '', t_lower)
|
|
67
|
-
if unprefixed and len(unprefixed) >= 3:
|
|
89
|
+
if unprefixed and len(unprefixed) >= 3 and unprefixed not in stop_words:
|
|
68
90
|
if (unprefixed in tokens or
|
|
69
91
|
f"{unprefixed}s" in tokens or
|
|
70
92
|
f"{unprefixed}es" in tokens or
|
|
@@ -73,8 +95,10 @@ class BaseConnector(ABC):
|
|
|
73
95
|
exact.append(t)
|
|
74
96
|
continue
|
|
75
97
|
|
|
76
|
-
# 3. Fuzzy partial match
|
|
98
|
+
# 3. Fuzzy partial match (strictly exclude English stop words)
|
|
77
99
|
for tok in tokens:
|
|
100
|
+
if tok in stop_words:
|
|
101
|
+
continue
|
|
78
102
|
if len(tok) >= 4 and (tok in t_lower or t_lower in tok):
|
|
79
103
|
if t not in exact and t not in fuzzy:
|
|
80
104
|
fuzzy.append(t)
|
|
@@ -42,6 +42,8 @@ class GroundTruthValidationResult:
|
|
|
42
42
|
lines.append(f" [dim]• ... and {len(self.verified_fks) - 4} more[/dim]")
|
|
43
43
|
if self.sql_queries_checked > 0:
|
|
44
44
|
lines.append(f" [bold green]✓[/bold green] [bold white]SQL Queries Verified:[/bold white] {self.sql_queries_checked} dialect statement(s) validated against schema")
|
|
45
|
+
for missing in self.missing_tables:
|
|
46
|
+
lines.append(f" [bold yellow]⚠ Note:[/bold yellow] Query referenced [dim]{missing}[/dim]")
|
|
45
47
|
for tbl, cols in self.missing_columns.items():
|
|
46
48
|
lines.append(f" [bold red]⚠ Warning:[/bold red] Column(s) [yellow]{', '.join(cols)}[/yellow] not found in table [cyan]{tbl}[/cyan]")
|
|
47
49
|
return "\n".join(lines)
|
|
@@ -83,14 +85,40 @@ class SchemaGroundTruthValidator:
|
|
|
83
85
|
if fk_desc not in result.verified_fks:
|
|
84
86
|
result.verified_fks.append(fk_desc)
|
|
85
87
|
|
|
86
|
-
# 2. Extract and
|
|
88
|
+
# 2. Extract and validate SQL queries against physical schema
|
|
87
89
|
sql_blocks = re.findall(r'```(?:sql)?\s*([\s\S]*?)\s*```', response_text, re.IGNORECASE)
|
|
88
90
|
result.sql_queries_checked = len(sql_blocks)
|
|
91
|
+
sql_referenced_tables = set()
|
|
92
|
+
|
|
93
|
+
for block in sql_blocks:
|
|
94
|
+
# Extract FROM and JOIN table references
|
|
95
|
+
matches = re.findall(r'\b(?:FROM|JOIN)\s+([a-zA-Z0-9_]+)', block, re.IGNORECASE)
|
|
96
|
+
for m in matches:
|
|
97
|
+
m_clean = m.strip().lower()
|
|
98
|
+
sql_referenced_tables.add(m_clean)
|
|
99
|
+
|
|
100
|
+
for sql_tbl in sql_referenced_tables:
|
|
101
|
+
if sql_tbl in schema_table_map:
|
|
102
|
+
if schema_table_map[sql_tbl].name not in result.verified_tables:
|
|
103
|
+
result.verified_tables.append(schema_table_map[sql_tbl].name)
|
|
104
|
+
else:
|
|
105
|
+
# Check if it's a common alias or slight naming divergence
|
|
106
|
+
found_match = None
|
|
107
|
+
for real_tbl in schema_table_map:
|
|
108
|
+
if sql_tbl in real_tbl or real_tbl in sql_tbl:
|
|
109
|
+
found_match = schema_table_map[real_tbl].name
|
|
110
|
+
break
|
|
111
|
+
if found_match:
|
|
112
|
+
result.missing_tables.append(f"{sql_tbl} ➔ (Actual: {found_match})")
|
|
113
|
+
else:
|
|
114
|
+
result.missing_tables.append(sql_tbl)
|
|
89
115
|
|
|
90
116
|
total_checked = len(result.verified_tables)
|
|
91
|
-
if
|
|
117
|
+
if result.missing_tables:
|
|
118
|
+
result.score_pct = max(70, 100 - (len(result.missing_tables) * 15))
|
|
119
|
+
elif total_checked > 0:
|
|
92
120
|
result.score_pct = 100
|
|
93
121
|
else:
|
|
94
122
|
result.score_pct = 95
|
|
95
|
-
|
|
123
|
+
|
|
96
124
|
return result
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "dbagent-cli"
|
|
7
|
-
version = "0.9.
|
|
7
|
+
version = "0.9.5"
|
|
8
8
|
description = "Enterprise AI Database Copilot, Schema Graph Introspector & Autonomous Recovery Agent (CLI)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|