nl2sql-engine 0.1.0__py3-none-any.whl
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.
- nl2sql/__init__.py +38 -0
- nl2sql/adapters/__init__.py +0 -0
- nl2sql/adapters/duckdb/__init__.py +0 -0
- nl2sql/adapters/duckdb/adapter.py +71 -0
- nl2sql/adapters/mssql/__init__.py +0 -0
- nl2sql/adapters/mssql/adapter.py +122 -0
- nl2sql/adapters/mysql/__init__.py +0 -0
- nl2sql/adapters/mysql/adapter.py +123 -0
- nl2sql/adapters/postgres/__init__.py +0 -0
- nl2sql/adapters/postgres/adapter.py +115 -0
- nl2sql/adapters/sqlalchemy_base/__init__.py +17 -0
- nl2sql/adapters/sqlalchemy_base/adapter.py +476 -0
- nl2sql/adapters/sqlalchemy_base/models.py +36 -0
- nl2sql/adapters/sqlite/__init__.py +0 -0
- nl2sql/adapters/sqlite/adapter.py +88 -0
- nl2sql/aggregation/__init__.py +3 -0
- nl2sql/aggregation/aggregator.py +98 -0
- nl2sql/aggregation/engines/__init__.py +3 -0
- nl2sql/aggregation/engines/polars_duckdb.py +125 -0
- nl2sql/api/__init__.py +0 -0
- nl2sql/api/auth_api.py +60 -0
- nl2sql/api/benchmark_api.py +114 -0
- nl2sql/api/datasource_api.py +132 -0
- nl2sql/api/indexing_api.py +59 -0
- nl2sql/api/llm_api.py +82 -0
- nl2sql/api/policy_api.py +135 -0
- nl2sql/api/query_api.py +138 -0
- nl2sql/api/result_api.py +24 -0
- nl2sql/api/settings_api.py +65 -0
- nl2sql/auth/__init__.py +8 -0
- nl2sql/auth/models.py +36 -0
- nl2sql/auth/rbac.py +25 -0
- nl2sql/cli/__init__.py +0 -0
- nl2sql/cli/checks.py +53 -0
- nl2sql/cli/commands/__init__.py +0 -0
- nl2sql/cli/commands/benchmark.py +34 -0
- nl2sql/cli/commands/doctor.py +49 -0
- nl2sql/cli/commands/indexing.py +126 -0
- nl2sql/cli/commands/info.py +25 -0
- nl2sql/cli/commands/install.py +27 -0
- nl2sql/cli/commands/policy.py +57 -0
- nl2sql/cli/commands/run.py +166 -0
- nl2sql/cli/commands/setup.py +415 -0
- nl2sql/cli/commands/visualize.py +34 -0
- nl2sql/cli/common/decorators.py +34 -0
- nl2sql/cli/config.py +24 -0
- nl2sql/cli/console.py +52 -0
- nl2sql/cli/demo/__init__.py +1 -0
- nl2sql/cli/demo/data.py +87 -0
- nl2sql/cli/demo/defaults.py +122 -0
- nl2sql/cli/demo/factory.py +289 -0
- nl2sql/cli/demo/manager.py +230 -0
- nl2sql/cli/demo/schemas.py +336 -0
- nl2sql/cli/demo/writers/__init__.py +0 -0
- nl2sql/cli/demo/writers/docker.py +182 -0
- nl2sql/cli/demo/writers/sqlite.py +88 -0
- nl2sql/cli/generators/datasources/__init__.py +3 -0
- nl2sql/cli/generators/datasources/generator.py +24 -0
- nl2sql/cli/generators/datasources/templates.py +7 -0
- nl2sql/cli/generators/env/__init__.py +3 -0
- nl2sql/cli/generators/env/generator.py +46 -0
- nl2sql/cli/generators/env/templates.py +25 -0
- nl2sql/cli/generators/llm/__init__.py +3 -0
- nl2sql/cli/generators/llm/generator.py +24 -0
- nl2sql/cli/generators/llm/templates.py +4 -0
- nl2sql/cli/generators/policies/__init__.py +3 -0
- nl2sql/cli/generators/policies/generator.py +20 -0
- nl2sql/cli/generators/policies/templates.py +2 -0
- nl2sql/cli/main.py +195 -0
- nl2sql/cli/reporting.py +878 -0
- nl2sql/cli/types.py +13 -0
- nl2sql/common/__init__.py +1 -0
- nl2sql/common/cancellation.py +25 -0
- nl2sql/common/context.py +5 -0
- nl2sql/common/errors.py +109 -0
- nl2sql/common/event_logger.py +88 -0
- nl2sql/common/exceptions.py +3 -0
- nl2sql/common/logger.py +119 -0
- nl2sql/common/metrics.py +50 -0
- nl2sql/common/resilience.py +59 -0
- nl2sql/common/settings.py +195 -0
- nl2sql/configs/__init__.py +6 -0
- nl2sql/configs/datasources.py +10 -0
- nl2sql/configs/llm.py +36 -0
- nl2sql/configs/manager.py +176 -0
- nl2sql/configs/policies.py +14 -0
- nl2sql/configs/sample_questions.py +11 -0
- nl2sql/configs/secrets.py +11 -0
- nl2sql/context.py +106 -0
- nl2sql/datasources/__init__.py +21 -0
- nl2sql/datasources/discovery.py +28 -0
- nl2sql/datasources/models.py +21 -0
- nl2sql/datasources/protocols.py +3 -0
- nl2sql/datasources/registry.py +172 -0
- nl2sql/evaluation/__init__.py +6 -0
- nl2sql/evaluation/benchmark_runner.py +320 -0
- nl2sql/evaluation/evaluator.py +134 -0
- nl2sql/evaluation/types.py +22 -0
- nl2sql/execution/__init__.py +4 -0
- nl2sql/execution/artifacts/__init__.py +3 -0
- nl2sql/execution/artifacts/parquet.py +41 -0
- nl2sql/execution/artifacts/store.py +165 -0
- nl2sql/execution/contracts.py +57 -0
- nl2sql/execution/execution_store.py +25 -0
- nl2sql/execution/executor/__init__.py +3 -0
- nl2sql/execution/executor/sql_executor.py +116 -0
- nl2sql/indexing/__init__.py +7 -0
- nl2sql/indexing/chunk_builder.py +227 -0
- nl2sql/indexing/embeddings.py +180 -0
- nl2sql/indexing/enrichment_service.py +316 -0
- nl2sql/indexing/models.py +209 -0
- nl2sql/indexing/orchestrator.py +90 -0
- nl2sql/indexing/vector_store.py +422 -0
- nl2sql/llm/__init__.py +8 -0
- nl2sql/llm/models.py +10 -0
- nl2sql/llm/registry.py +214 -0
- nl2sql/pipeline/__init__.py +1 -0
- nl2sql/pipeline/graph.py +73 -0
- nl2sql/pipeline/graph_utils.py +141 -0
- nl2sql/pipeline/nodes/__init__.py +25 -0
- nl2sql/pipeline/nodes/aggregator/__init__.py +4 -0
- nl2sql/pipeline/nodes/aggregator/node.py +55 -0
- nl2sql/pipeline/nodes/aggregator/prompts.py +20 -0
- nl2sql/pipeline/nodes/aggregator/schemas.py +28 -0
- nl2sql/pipeline/nodes/answer_synthesizer/__init__.py +4 -0
- nl2sql/pipeline/nodes/answer_synthesizer/node.py +98 -0
- nl2sql/pipeline/nodes/answer_synthesizer/prompts.py +19 -0
- nl2sql/pipeline/nodes/answer_synthesizer/schemas.py +24 -0
- nl2sql/pipeline/nodes/ast_planner/__init__.py +4 -0
- nl2sql/pipeline/nodes/ast_planner/node.py +104 -0
- nl2sql/pipeline/nodes/ast_planner/prompts.py +138 -0
- nl2sql/pipeline/nodes/ast_planner/schemas.py +236 -0
- nl2sql/pipeline/nodes/datasource_resolver/__init__.py +4 -0
- nl2sql/pipeline/nodes/datasource_resolver/node.py +253 -0
- nl2sql/pipeline/nodes/datasource_resolver/schemas.py +21 -0
- nl2sql/pipeline/nodes/decomposer/__init__.py +3 -0
- nl2sql/pipeline/nodes/decomposer/node.py +219 -0
- nl2sql/pipeline/nodes/decomposer/prompts.py +96 -0
- nl2sql/pipeline/nodes/decomposer/schemas.py +143 -0
- nl2sql/pipeline/nodes/executor/__init__.py +3 -0
- nl2sql/pipeline/nodes/executor/node.py +107 -0
- nl2sql/pipeline/nodes/generator/__init__.py +4 -0
- nl2sql/pipeline/nodes/generator/node.py +267 -0
- nl2sql/pipeline/nodes/generator/schemas.py +13 -0
- nl2sql/pipeline/nodes/global_planner/__init__.py +4 -0
- nl2sql/pipeline/nodes/global_planner/node.py +186 -0
- nl2sql/pipeline/nodes/global_planner/schemas.py +101 -0
- nl2sql/pipeline/nodes/refiner/__init__.py +4 -0
- nl2sql/pipeline/nodes/refiner/node.py +132 -0
- nl2sql/pipeline/nodes/refiner/prompts.py +28 -0
- nl2sql/pipeline/nodes/refiner/schemas.py +13 -0
- nl2sql/pipeline/nodes/schema_retriever/__init__.py +3 -0
- nl2sql/pipeline/nodes/schema_retriever/node.py +252 -0
- nl2sql/pipeline/nodes/schema_retriever/schema.py +27 -0
- nl2sql/pipeline/nodes/validator/__init__.py +7 -0
- nl2sql/pipeline/nodes/validator/node.py +839 -0
- nl2sql/pipeline/nodes/validator/schemas.py +12 -0
- nl2sql/pipeline/pipeline_runner.py +72 -0
- nl2sql/pipeline/routes.py +72 -0
- nl2sql/pipeline/runtime.py +153 -0
- nl2sql/pipeline/state.py +92 -0
- nl2sql/pipeline/subgraphs/__init__.py +5 -0
- nl2sql/pipeline/subgraphs/schemas.py +23 -0
- nl2sql/pipeline/subgraphs/sql_agent.py +167 -0
- nl2sql/public_api.py +199 -0
- nl2sql/schema/__init__.py +37 -0
- nl2sql/schema/in_memory_store.py +173 -0
- nl2sql/schema/protocol.py +88 -0
- nl2sql/schema/sqlite_store.py +233 -0
- nl2sql/schema/store.py +29 -0
- nl2sql/secrets/__init__.py +14 -0
- nl2sql/secrets/factory.py +85 -0
- nl2sql/secrets/interfaces.py +16 -0
- nl2sql/secrets/manager.py +139 -0
- nl2sql/secrets/models.py +56 -0
- nl2sql/secrets/providers/aws.py +30 -0
- nl2sql/secrets/providers/azure.py +49 -0
- nl2sql/secrets/providers/env.py +8 -0
- nl2sql/secrets/providers/hashi.py +46 -0
- nl2sql/services/__init__.py +0 -0
- nl2sql/services/callbacks/__init__.py +0 -0
- nl2sql/services/callbacks/monitor.py +84 -0
- nl2sql/services/callbacks/node_context.py +7 -0
- nl2sql/services/callbacks/node_handlers.py +187 -0
- nl2sql/services/callbacks/node_metrics.py +14 -0
- nl2sql/services/callbacks/presenter.py +12 -0
- nl2sql/services/callbacks/token_handler.py +56 -0
- nl2sql_engine-0.1.0.dist-info/METADATA +295 -0
- nl2sql_engine-0.1.0.dist-info/RECORD +192 -0
- nl2sql_engine-0.1.0.dist-info/WHEEL +5 -0
- nl2sql_engine-0.1.0.dist-info/entry_points.txt +9 -0
- nl2sql_engine-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
|
|
2
|
+
import os
|
|
3
|
+
import pathlib
|
|
4
|
+
from rich.markup import escape
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
from InquirerPy import inquirer
|
|
7
|
+
from InquirerPy.validator import NumberValidator
|
|
8
|
+
|
|
9
|
+
from nl2sql.cli.common.decorators import handle_cli_errors
|
|
10
|
+
from nl2sql.cli.console import console, print_success, print_step
|
|
11
|
+
from nl2sql.cli.config import ADAPTER_DRIVERS, KNOWN_ADAPTERS
|
|
12
|
+
from nl2sql.cli.commands.install import install_package
|
|
13
|
+
from nl2sql.cli.checks import check_package, verify_connectivity
|
|
14
|
+
|
|
15
|
+
from nl2sql.common.logger import get_logger
|
|
16
|
+
from nl2sql.configs import ConfigManager
|
|
17
|
+
from nl2sql.configs import (
|
|
18
|
+
DatasourceConfig,
|
|
19
|
+
DatasourceFileConfig,
|
|
20
|
+
ConnectionConfig,
|
|
21
|
+
LLMFileConfig,
|
|
22
|
+
AgentConfig,
|
|
23
|
+
PolicyFileConfig,
|
|
24
|
+
RolePolicy
|
|
25
|
+
)
|
|
26
|
+
from nl2sql.cli.demo import DemoManager
|
|
27
|
+
|
|
28
|
+
logger = get_logger(__name__)
|
|
29
|
+
|
|
30
|
+
# The CLI writes where it is invoked. Do not resolve this from __file__:
|
|
31
|
+
# that walks up out of the installed module and, in a source checkout,
|
|
32
|
+
# lands on the repo root instead of the user's working directory.
|
|
33
|
+
PROJECT_ROOT = pathlib.Path.cwd()
|
|
34
|
+
|
|
35
|
+
CONFIG_DIR = PROJECT_ROOT / "configs"
|
|
36
|
+
DATASOURCE_CONFIG = CONFIG_DIR / "datasources.yaml"
|
|
37
|
+
LLM_CONFIG = CONFIG_DIR / "llm.yaml"
|
|
38
|
+
POLICIES_CONFIG = CONFIG_DIR / "policies.json"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
from typing import Optional
|
|
42
|
+
from nl2sql.configs import DatasourceConfig, ConnectionConfig
|
|
43
|
+
|
|
44
|
+
def _configure_datasource(config_manager: ConfigManager):
|
|
45
|
+
"""Interactively configures datasources."""
|
|
46
|
+
|
|
47
|
+
if DATASOURCE_CONFIG.exists():
|
|
48
|
+
console.print(Panel("[bold]1. Datasource Configuration[/bold]", border_style="cyan"))
|
|
49
|
+
console.print("[dim]Existing configuration found.[/dim]")
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
console.print(Panel("[bold]1. Datasource Configuration[/bold]", border_style="cyan"))
|
|
53
|
+
console.print("No datasource configuration found. Let's create one.")
|
|
54
|
+
|
|
55
|
+
db_type = inquirer.select(
|
|
56
|
+
message="Select Database Type:",
|
|
57
|
+
choices=["postgres", "mysql", "mssql", "sqlite"],
|
|
58
|
+
default="postgres"
|
|
59
|
+
).execute()
|
|
60
|
+
|
|
61
|
+
ds_config = None
|
|
62
|
+
|
|
63
|
+
if db_type == "sqlite":
|
|
64
|
+
db_path = inquirer.text(message="Database Path:", default="./my_database.db").execute()
|
|
65
|
+
conn = ConnectionConfig(type="sqlite", database=db_path)
|
|
66
|
+
ds_config = DatasourceConfig(
|
|
67
|
+
id="my_sqlite_db",
|
|
68
|
+
description="Main application database",
|
|
69
|
+
connection=conn
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
host = inquirer.text(message="Host:", default="localhost").execute()
|
|
73
|
+
default_ports = {"postgres": "5432", "mysql": "3306", "mssql": "1433"}
|
|
74
|
+
port = inquirer.text(
|
|
75
|
+
message="Port:",
|
|
76
|
+
default=default_ports.get(db_type, "5432"),
|
|
77
|
+
validate=NumberValidator()
|
|
78
|
+
).execute()
|
|
79
|
+
|
|
80
|
+
user = inquirer.text(
|
|
81
|
+
message="Username:",
|
|
82
|
+
default="postgres" if db_type == "postgres" else "root"
|
|
83
|
+
).execute()
|
|
84
|
+
|
|
85
|
+
dbname = inquirer.text(message="Database Name:").execute()
|
|
86
|
+
|
|
87
|
+
# Password & Secrets
|
|
88
|
+
password = inquirer.secret(message="Password:").execute()
|
|
89
|
+
final_password = password
|
|
90
|
+
|
|
91
|
+
if inquirer.confirm(message="Secure this password with an Environment Variable?", default=True).execute():
|
|
92
|
+
env_var = inquirer.text(message="Environment Variable Name:", default="DB_PASSWORD").execute()
|
|
93
|
+
final_password = f"${{env:{env_var}}}"
|
|
94
|
+
console.print(f"[dim]Will save as: {escape(str(final_password))}[/dim]")
|
|
95
|
+
os.environ[env_var] = password # Set it for current session so validation passes
|
|
96
|
+
|
|
97
|
+
conn_args = {
|
|
98
|
+
"type": db_type,
|
|
99
|
+
"host": host,
|
|
100
|
+
"port": int(port),
|
|
101
|
+
"user": user,
|
|
102
|
+
"password": final_password,
|
|
103
|
+
"database": dbname
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if db_type == "mssql":
|
|
107
|
+
conn_args["driver"] = "ODBC Driver 17 for SQL Server"
|
|
108
|
+
|
|
109
|
+
conn = ConnectionConfig(**conn_args)
|
|
110
|
+
|
|
111
|
+
ds_config = DatasourceConfig(
|
|
112
|
+
id="main_db",
|
|
113
|
+
connection=conn,
|
|
114
|
+
options={}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Write using Manager
|
|
118
|
+
# Write using Generator
|
|
119
|
+
ds_configs = [ds_config]
|
|
120
|
+
if ds_configs:
|
|
121
|
+
console.print(f"[green]Generated configuration for {len(ds_configs)} datasources.[/green]")
|
|
122
|
+
file_config = DatasourceFileConfig(datasources=ds_configs)
|
|
123
|
+
content = DatasourceGenerator.generate(file_config)
|
|
124
|
+
_write_config_file(DATASOURCE_CONFIG, content)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
from nl2sql.configs import LLMFileConfig, AgentConfig
|
|
128
|
+
from nl2sql.cli.generators.llm import LLMGenerator
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _configure_llm(config_manager: ConfigManager, api_key: Optional[str] = None):
|
|
132
|
+
"""Interactively configures LLM."""
|
|
133
|
+
if LLM_CONFIG.exists():
|
|
134
|
+
console.print(Panel("[bold]2. LLM Configuration[/bold]", border_style="magenta"))
|
|
135
|
+
console.print("[dim]Existing configuration found.[/dim]")
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
console.print(Panel("[bold]2. LLM Configuration[/bold]", border_style="magenta"))
|
|
139
|
+
|
|
140
|
+
if api_key:
|
|
141
|
+
console.print("[green]API Key provided via CLI. Creating default OpenAI configuration.[/green]")
|
|
142
|
+
default_agent = AgentConfig(
|
|
143
|
+
provider="openai",
|
|
144
|
+
model="gpt-4o",
|
|
145
|
+
api_key="${env:OPENAI_API_KEY}"
|
|
146
|
+
)
|
|
147
|
+
llm_config = LLMFileConfig(default=default_agent)
|
|
148
|
+
content = LLMGenerator.generate(llm_config)
|
|
149
|
+
_write_config_file(LLM_CONFIG, content)
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
console.print("No LLM configuration found. Let's configure one.")
|
|
153
|
+
|
|
154
|
+
# Only providers the engine can actually serve are offered here; anything
|
|
155
|
+
# else lets setup succeed and then fails on the first query.
|
|
156
|
+
provider = inquirer.select(
|
|
157
|
+
message="Select Provider:",
|
|
158
|
+
choices=["openai", "openrouter"],
|
|
159
|
+
default="openai"
|
|
160
|
+
).execute()
|
|
161
|
+
|
|
162
|
+
default_agent = None
|
|
163
|
+
|
|
164
|
+
if provider == "openai":
|
|
165
|
+
api_key = inquirer.secret(message="OpenAI API Key:").execute()
|
|
166
|
+
default_agent = AgentConfig(
|
|
167
|
+
provider="openai",
|
|
168
|
+
model="gpt-4o",
|
|
169
|
+
api_key=api_key
|
|
170
|
+
)
|
|
171
|
+
elif provider == "openrouter":
|
|
172
|
+
console.print(
|
|
173
|
+
"[dim]OpenRouter is an OpenAI-compatible gateway: one key reaches "
|
|
174
|
+
"Anthropic, Google, Meta and hundreds of other models.[/dim]"
|
|
175
|
+
)
|
|
176
|
+
api_key = inquirer.secret(message="OpenRouter API Key:").execute()
|
|
177
|
+
model = inquirer.text(
|
|
178
|
+
message="OpenRouter model identifier (e.g. anthropic/claude-sonnet-4.5):",
|
|
179
|
+
default="anthropic/claude-sonnet-4.5"
|
|
180
|
+
).execute()
|
|
181
|
+
env_var = "OPENROUTER_API_KEY"
|
|
182
|
+
os.environ[env_var] = api_key # available to the rest of this session
|
|
183
|
+
default_agent = AgentConfig(
|
|
184
|
+
provider="openrouter",
|
|
185
|
+
model=model,
|
|
186
|
+
api_key=f"${{env:{env_var}}}"
|
|
187
|
+
)
|
|
188
|
+
console.print(f"[dim]Will save the key as: ${{env:{env_var}}}[/dim]")
|
|
189
|
+
console.print(
|
|
190
|
+
"[yellow]Note:[/yellow] embeddings still go through OpenAI, so "
|
|
191
|
+
"[cyan]nl2sql index[/cyan] needs OPENAI_API_KEY as well."
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
llm_config = LLMFileConfig(default=default_agent)
|
|
195
|
+
content = LLMGenerator.generate(llm_config)
|
|
196
|
+
_write_config_file(LLM_CONFIG, content)
|
|
197
|
+
|
|
198
|
+
from nl2sql.configs import PolicyFileConfig, RolePolicy
|
|
199
|
+
|
|
200
|
+
def _configure_policies(config_manager: ConfigManager):
|
|
201
|
+
"""Generates default policies."""
|
|
202
|
+
if POLICIES_CONFIG.exists():
|
|
203
|
+
console.print(Panel("[bold]3. Policy Configuration[/bold]", border_style="yellow"))
|
|
204
|
+
console.print("[dim]Existing policies configuration found.[/dim]")
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
console.print(Panel("[bold]3. Policy Configuration[/bold]", border_style="yellow"))
|
|
208
|
+
console.print("Generating default RBAC policies...")
|
|
209
|
+
|
|
210
|
+
admin_policy = RolePolicy(
|
|
211
|
+
description="System Administrator",
|
|
212
|
+
role="admin",
|
|
213
|
+
allowed_datasources=["*"],
|
|
214
|
+
allowed_tables=["*"]
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
policy_config = PolicyFileConfig(roles={"admin": admin_policy})
|
|
218
|
+
|
|
219
|
+
content = PolicyGenerator.generate(policy_config)
|
|
220
|
+
_write_config_file(POLICIES_CONFIG, content)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
from nl2sql.cli.generators.env import EnvFileGenerator
|
|
225
|
+
from nl2sql.cli.generators.datasources import DatasourceGenerator
|
|
226
|
+
from nl2sql.cli.generators.llm import LLMGenerator
|
|
227
|
+
from nl2sql.cli.generators.policies import PolicyGenerator
|
|
228
|
+
|
|
229
|
+
def _write_config_file(path: pathlib.Path, content: str):
|
|
230
|
+
"""Helper to write generator output to file."""
|
|
231
|
+
try:
|
|
232
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
233
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
234
|
+
f.write(content)
|
|
235
|
+
print_success(f"Created {path}")
|
|
236
|
+
except Exception as e:
|
|
237
|
+
console.print(f"[red]Failed to write {escape(str(path.name))}: {escape(str(e))}[/red]")
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _configure_env_file(env: str, api_key: Optional[str] = None):
|
|
241
|
+
"""Creates the .env.{env} file using the Universal Environment Protocol."""
|
|
242
|
+
target_file = PROJECT_ROOT / f".env.{env}"
|
|
243
|
+
|
|
244
|
+
if target_file.exists():
|
|
245
|
+
console.print(Panel(f"[bold]Environment Configuration ({env})[/bold]", border_style="blue"))
|
|
246
|
+
console.print(f"[dim]Existing {target_file.name} found.[/dim]")
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
console.print(Panel(f"[bold]Environment Configuration ({env})[/bold]", border_style="blue"))
|
|
250
|
+
console.print(f"Creating explicit configuration file: {target_file.name}")
|
|
251
|
+
|
|
252
|
+
# Generate Content
|
|
253
|
+
secrets = {}
|
|
254
|
+
if api_key:
|
|
255
|
+
secrets["OPENAI_API_KEY"] = api_key
|
|
256
|
+
|
|
257
|
+
content = EnvFileGenerator.generate(env, secrets=secrets)
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
with open(target_file, "w", encoding="utf-8") as f:
|
|
261
|
+
f.write(content)
|
|
262
|
+
print_success(f"Created {target_file}")
|
|
263
|
+
except Exception as e:
|
|
264
|
+
console.print(f"[red]Failed to write env file: {escape(str(e))}[/red]")
|
|
265
|
+
|
|
266
|
+
def _install_required_adapters(config_manager: ConfigManager):
|
|
267
|
+
"""Reads config using ConfigManager and installs necessary adapters."""
|
|
268
|
+
if not DATASOURCE_CONFIG.exists():
|
|
269
|
+
return
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
# Use ConfigManager to load standardized objects
|
|
273
|
+
# load_datasources returns List[Dict]
|
|
274
|
+
configs = config_manager.load_datasources()
|
|
275
|
+
|
|
276
|
+
required = set()
|
|
277
|
+
for config in configs:
|
|
278
|
+
connection = config.get("connection", {})
|
|
279
|
+
engine = connection.get("type", "").lower() or config.get("type", "").lower()
|
|
280
|
+
|
|
281
|
+
for name, pkg in KNOWN_ADAPTERS.items():
|
|
282
|
+
if name in engine:
|
|
283
|
+
required.add((name, pkg))
|
|
284
|
+
break
|
|
285
|
+
|
|
286
|
+
if required:
|
|
287
|
+
print_step("Checking Adapters...")
|
|
288
|
+
for name, pkg in sorted(required):
|
|
289
|
+
# The adapter ships with nl2sql; the extra supplies the driver.
|
|
290
|
+
if not check_package(ADAPTER_DRIVERS[name]):
|
|
291
|
+
if inquirer.confirm(message=f"Required adapter {pkg} is missing. Install now?", default=True).execute():
|
|
292
|
+
console.print(f"[yellow]Installing {escape(pkg)}...[/yellow]")
|
|
293
|
+
install_package(pkg)
|
|
294
|
+
else:
|
|
295
|
+
console.print(f"[dim]Adapter {escape(pkg)} is installed.[/dim]")
|
|
296
|
+
|
|
297
|
+
except Exception as e:
|
|
298
|
+
console.print(f"[red]Failed to check adapters: {escape(str(e))}[/red]")
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _run_indexing_step():
|
|
302
|
+
"""Offers schema indexing and runs it through a context built from config paths."""
|
|
303
|
+
from nl2sql.common.settings import settings
|
|
304
|
+
from nl2sql.context import NL2SQLContext
|
|
305
|
+
from nl2sql.indexing.vector_store import VectorStore
|
|
306
|
+
from nl2sql.cli.commands.indexing import run_indexing
|
|
307
|
+
|
|
308
|
+
try:
|
|
309
|
+
v_store = VectorStore(
|
|
310
|
+
collection_name=settings.vector_store_collection_name,
|
|
311
|
+
persist_directory=str(PROJECT_ROOT / settings.vector_store_path),
|
|
312
|
+
)
|
|
313
|
+
should_index = False
|
|
314
|
+
|
|
315
|
+
if not v_store.is_empty():
|
|
316
|
+
console.print("[yellow]Vector Store already contains data.[/yellow]")
|
|
317
|
+
if inquirer.confirm(message="Do you want to clear and re-index?", default=False).execute():
|
|
318
|
+
should_index = True
|
|
319
|
+
else:
|
|
320
|
+
if inquirer.confirm(message="Vector Store is empty. Run Schema Indexing now?", default=True).execute():
|
|
321
|
+
should_index = True
|
|
322
|
+
|
|
323
|
+
if not should_index:
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
print_step("Starting Indexer...")
|
|
327
|
+
|
|
328
|
+
# NL2SQLContext builds the datasource, LLM and vector store registries
|
|
329
|
+
# itself; it takes config *paths*, not pre-built registries.
|
|
330
|
+
ctx = NL2SQLContext(
|
|
331
|
+
ds_config_path=PROJECT_ROOT / settings.datasource_config_path,
|
|
332
|
+
secrets_config_path=PROJECT_ROOT / settings.secrets_config_path,
|
|
333
|
+
llm_config_path=PROJECT_ROOT / settings.llm_config_path,
|
|
334
|
+
vector_store_path=PROJECT_ROOT / settings.vector_store_path,
|
|
335
|
+
policies_config_path=PROJECT_ROOT / settings.policies_config_path,
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
run_indexing(ctx)
|
|
339
|
+
print_success("Indexing process finished.")
|
|
340
|
+
|
|
341
|
+
except Exception as e:
|
|
342
|
+
logger.debug("Indexing setup failed", exc_info=True)
|
|
343
|
+
console.print(f"[red]Indexing setup failed: {escape(str(e))}[/red]")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@handle_cli_errors
|
|
347
|
+
def setup_command(demo: bool = False, lite: bool = True, docker: bool = False, api_key: Optional[str] = None):
|
|
348
|
+
|
|
349
|
+
# Instantiate Managers
|
|
350
|
+
config_manager = ConfigManager(PROJECT_ROOT)
|
|
351
|
+
demo_manager = DemoManager(console, PROJECT_ROOT)
|
|
352
|
+
|
|
353
|
+
if demo:
|
|
354
|
+
console.print(Panel("[bold green]Setting up Demo Environment...[/bold green]", border_style="green"))
|
|
355
|
+
|
|
356
|
+
if lite:
|
|
357
|
+
demo_manager.setup_lite(api_key=api_key)
|
|
358
|
+
elif docker:
|
|
359
|
+
docker_dir = demo_manager.setup_docker(api_key=api_key)
|
|
360
|
+
if inquirer.confirm(message="Start Docker containers now?", default=True).execute():
|
|
361
|
+
demo_manager.start_docker_containers(docker_dir)
|
|
362
|
+
|
|
363
|
+
console.print(Panel(f"""[bold yellow]Next Steps:[/bold yellow]
|
|
364
|
+
1. [bold]Verify & Index[/bold]:
|
|
365
|
+
Once database containers are healthy (~30s), run:
|
|
366
|
+
[cyan]nl2sql --env demo index[/cyan]
|
|
367
|
+
|
|
368
|
+
2. [bold]API[/bold]: the 'app' container serves the REST API on
|
|
369
|
+
[cyan]http://localhost:8000[/cyan].
|
|
370
|
+
|
|
371
|
+
3. [bold]MSSQL[/bold] is opt-in:
|
|
372
|
+
[cyan]docker compose -f docker-compose.demo.yml --profile mssql up -d[/cyan]
|
|
373
|
+
""", title="Docker Instructions", border_style="yellow")
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
if not docker:
|
|
377
|
+
print_step("Indexing Demo Environment...")
|
|
378
|
+
demo_manager.index_demo_data()
|
|
379
|
+
console.print("Run: [cyan]nl2sql --env demo run \"Show me broken machines in Austin\"[/cyan]")
|
|
380
|
+
|
|
381
|
+
return
|
|
382
|
+
|
|
383
|
+
# --- Standard Setup Wizard ---
|
|
384
|
+
console.print("[bold cyan]NL2SQL Setup Wizard[/bold cyan]\n")
|
|
385
|
+
|
|
386
|
+
config_manager.ensure_config_dirs()
|
|
387
|
+
|
|
388
|
+
# 1. Environment File
|
|
389
|
+
_configure_env_file("dev", api_key=api_key)
|
|
390
|
+
|
|
391
|
+
# 2. Datasource
|
|
392
|
+
_configure_datasource(config_manager)
|
|
393
|
+
|
|
394
|
+
# 2. LLM
|
|
395
|
+
_configure_llm(config_manager, api_key=api_key)
|
|
396
|
+
|
|
397
|
+
# 3. Policies
|
|
398
|
+
_configure_policies(config_manager)
|
|
399
|
+
|
|
400
|
+
# 4. Adapters
|
|
401
|
+
_install_required_adapters(config_manager)
|
|
402
|
+
|
|
403
|
+
# 5. Connectivity Check
|
|
404
|
+
print_step("Checking Database Connectivity...")
|
|
405
|
+
if not verify_connectivity(print_table=True):
|
|
406
|
+
console.print("[yellow]Warning: Some datasources are failing validation.[/yellow]")
|
|
407
|
+
if not inquirer.confirm(message="Continue anyway?", default=False).execute():
|
|
408
|
+
return
|
|
409
|
+
|
|
410
|
+
# 6. Indexing Prompt
|
|
411
|
+
console.print("")
|
|
412
|
+
_run_indexing_step()
|
|
413
|
+
|
|
414
|
+
console.print("\n[bold green]Setup Complete![/bold green]")
|
|
415
|
+
console.print("Try running a query: [cyan]nl2sql run \"Show me all tables\"[/cyan]")
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import List, Dict, Any
|
|
2
|
+
from langgraph.graph.state import CompiledStateGraph
|
|
3
|
+
from nl2sql.cli.reporting import ConsolePresenter
|
|
4
|
+
|
|
5
|
+
from nl2sql.cli.common.decorators import handle_cli_errors
|
|
6
|
+
|
|
7
|
+
@handle_cli_errors
|
|
8
|
+
def draw_execution_trace(
|
|
9
|
+
trace: List[Dict[str, Any]],
|
|
10
|
+
graph: CompiledStateGraph,
|
|
11
|
+
execution_subgraph: CompiledStateGraph,
|
|
12
|
+
agentic_execution_loop: CompiledStateGraph
|
|
13
|
+
):
|
|
14
|
+
"""Visualizes the execution trace in the CLI and saves the graph structure.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
trace (List[Dict[str, Any]]): The execution trace.
|
|
18
|
+
graph (CompiledStateGraph): The main graph.
|
|
19
|
+
execution_subgraph (CompiledStateGraph): The execution subgraph.
|
|
20
|
+
agentic_execution_loop (CompiledStateGraph): The agentic loop subgraph.
|
|
21
|
+
"""
|
|
22
|
+
presenter = ConsolePresenter()
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
png_bytes = graph.get_graph(xray=True).draw_mermaid_png()
|
|
26
|
+
import os
|
|
27
|
+
output_path = "graph_trace.png"
|
|
28
|
+
abs_path = os.path.abspath(output_path)
|
|
29
|
+
with open(output_path, "wb") as f:
|
|
30
|
+
f.write(png_bytes)
|
|
31
|
+
|
|
32
|
+
presenter.print_graph_saved(abs_path)
|
|
33
|
+
except Exception as e:
|
|
34
|
+
presenter.print_graph_save_error(str(e))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
import sys
|
|
3
|
+
import traceback
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.markup import escape
|
|
6
|
+
from nl2sql.common.exceptions import NL2SQLError
|
|
7
|
+
|
|
8
|
+
console = Console()
|
|
9
|
+
|
|
10
|
+
def handle_cli_errors(func):
|
|
11
|
+
"""
|
|
12
|
+
Decorator to wrap CLI commands with unified error handling.
|
|
13
|
+
|
|
14
|
+
- NL2SQLError: Prints a clean red error message.
|
|
15
|
+
- KeyboardInterrupt: Exits gracefully.
|
|
16
|
+
- Unexpected Exception: Prints stack trace and error.
|
|
17
|
+
"""
|
|
18
|
+
@wraps(func)
|
|
19
|
+
def wrapper(*args, **kwargs):
|
|
20
|
+
try:
|
|
21
|
+
return func(*args, **kwargs)
|
|
22
|
+
except NL2SQLError as e:
|
|
23
|
+
console.print(f"[bold red]Error:[/bold red] {escape(str(e))}")
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
except KeyboardInterrupt:
|
|
26
|
+
console.print("\n[yellow]Operation cancelled by user.[/yellow]")
|
|
27
|
+
sys.exit(130) # Standard SIGINT exit code
|
|
28
|
+
except Exception as e:
|
|
29
|
+
console.print(f"[bold red]Unexpected Error:[/bold red] {escape(str(e))}")
|
|
30
|
+
console.print(traceback.format_exc(), markup=False)
|
|
31
|
+
console.print("[dim]Please report this bug to the nl2sql team.[/dim]")
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
|
|
34
|
+
return wrapper
|
nl2sql/cli/config.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
CORE_PACKAGE = "nl2sql"
|
|
3
|
+
CORE_MODULE = "nl2sql.cli"
|
|
4
|
+
|
|
5
|
+
# Every dialect adapter now ships inside the single `nl2sql-engine`
|
|
6
|
+
# distribution, so the adapter module is always importable. What is optional
|
|
7
|
+
# is the DB driver each one needs to actually connect, carried by an extra.
|
|
8
|
+
KNOWN_ADAPTERS = {
|
|
9
|
+
"sqlite": "nl2sql-engine",
|
|
10
|
+
"duckdb": "nl2sql-engine[duckdb]",
|
|
11
|
+
"postgresql": "nl2sql-engine[postgres]",
|
|
12
|
+
"mysql": "nl2sql-engine[mysql]",
|
|
13
|
+
"mssql": "nl2sql-engine[mssql]",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# The driver module that must import for the adapter above to be usable.
|
|
17
|
+
# sqlite needs none beyond the standard library.
|
|
18
|
+
ADAPTER_DRIVERS = {
|
|
19
|
+
"sqlite": "sqlite3",
|
|
20
|
+
"duckdb": "duckdb_engine",
|
|
21
|
+
"postgresql": "psycopg2",
|
|
22
|
+
"mysql": "pymysql",
|
|
23
|
+
"mssql": "pyodbc",
|
|
24
|
+
}
|
nl2sql/cli/console.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.markup import escape
|
|
5
|
+
from rich.theme import Theme
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def configure_output_encoding() -> None:
|
|
9
|
+
"""Make CLI output safe on a console whose code page is not UTF-8.
|
|
10
|
+
|
|
11
|
+
Rich renders symbols such as U+2713 that the default Windows code page
|
|
12
|
+
(cp1252) cannot encode, so writing them raised UnicodeEncodeError and
|
|
13
|
+
aborted the command -- `nl2sql setup --demo --lite` died this way.
|
|
14
|
+
Reconfiguring the streams once, at the entry point, is what
|
|
15
|
+
PYTHONIOENCODING=utf-8 did for users who knew to set it; rich reads
|
|
16
|
+
``sys.stdout`` and its encoding lazily, so consoles built at import time
|
|
17
|
+
pick this up too.
|
|
18
|
+
|
|
19
|
+
``errors="replace"`` is the backstop: if a stream cannot become UTF-8, an
|
|
20
|
+
unencodable symbol degrades to a placeholder instead of killing the
|
|
21
|
+
process. Streams that are not text wrappers -- pytest capture, some
|
|
22
|
+
redirections -- have no ``reconfigure`` and are left alone.
|
|
23
|
+
"""
|
|
24
|
+
for stream in (sys.stdout, sys.stderr):
|
|
25
|
+
reconfigure = getattr(stream, "reconfigure", None)
|
|
26
|
+
if reconfigure is None:
|
|
27
|
+
continue
|
|
28
|
+
try:
|
|
29
|
+
reconfigure(encoding="utf-8", errors="replace")
|
|
30
|
+
except (OSError, ValueError):
|
|
31
|
+
# A detached or already-closed stream. Nothing to configure.
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
custom_theme = Theme({
|
|
36
|
+
"info": "cyan",
|
|
37
|
+
"warning": "yellow",
|
|
38
|
+
"error": "bold red",
|
|
39
|
+
"success": "bold green",
|
|
40
|
+
"command": "bold white on blue",
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
console = Console(theme=custom_theme)
|
|
44
|
+
|
|
45
|
+
def print_step(message: str) -> None:
|
|
46
|
+
console.print(f"[bold blue]Step:[/bold blue] {escape(str(message))}")
|
|
47
|
+
|
|
48
|
+
def print_success(message: str) -> None:
|
|
49
|
+
console.print(f"[success][OK] {escape(str(message))}[/success]")
|
|
50
|
+
|
|
51
|
+
def print_error(message: str) -> None:
|
|
52
|
+
console.print(f"[error][ERROR] {escape(str(message))}[/error]")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .manager import DemoManager
|
nl2sql/cli/demo/data.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from typing import List, Dict, Any
|
|
2
|
+
|
|
3
|
+
# Shared Constants (The "Glue")
|
|
4
|
+
|
|
5
|
+
FACTORIES: List[Dict[str, Any]] = [
|
|
6
|
+
{"id": 1, "name": "Austin Gigafactory", "region": "US", "capacity": 5000},
|
|
7
|
+
{"id": 2, "name": "Berlin Plant", "region": "EU", "capacity": 3500},
|
|
8
|
+
{"id": 3, "name": "Shanghai Facility", "region": "CN", "capacity": 8000},
|
|
9
|
+
{"id": 4, "name": "Tokyo Hub", "region": "JP", "capacity": 4200},
|
|
10
|
+
{"id": 5, "name": "Mumbai Plant", "region": "IN", "capacity": 3000},
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
SHIFTS: List[Dict[str, Any]] = [
|
|
14
|
+
{"id": 1, "name": "Morning", "start_time": "06:00", "end_time": "14:00"},
|
|
15
|
+
{"id": 2, "name": "Evening", "start_time": "14:00", "end_time": "22:00"},
|
|
16
|
+
{"id": 3, "name": "Night", "start_time": "22:00", "end_time": "06:00"},
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
MACHINE_TYPES: List[Dict[str, Any]] = [
|
|
20
|
+
{"id": 1, "model": "Robotic Arm v1", "producer": "TechCorp", "maintenance_interval_days": 30},
|
|
21
|
+
{"id": 2, "model": "Conveyor Belt Gen3", "producer": "MoveIt", "maintenance_interval_days": 180},
|
|
22
|
+
{"id": 3, "model": "Stamping Press 500T", "producer": "HeavyMetal", "maintenance_interval_days": 90},
|
|
23
|
+
{"id": 4, "model": "Painting Station", "producer": "ColorsInc", "maintenance_interval_days": 14},
|
|
24
|
+
{"id": 5, "model": "Quality Scanner", "producer": "VisionAI", "maintenance_interval_days": 60},
|
|
25
|
+
{"id": 6, "model": "Laser Cutter L2", "producer": "PhotonWorks", "maintenance_interval_days": 45},
|
|
26
|
+
{"id": 7, "model": "CNC Mill Pro", "producer": "Machina", "maintenance_interval_days": 120},
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
PRODUCTS: List[Dict[str, Any]] = [
|
|
30
|
+
# High Value
|
|
31
|
+
{"id": 1, "sku": "IC-5000", "name": "Industrial Controller", "base_cost": 4500.00, "category": "Electronics"},
|
|
32
|
+
{"id": 2, "sku": "BAT-EV-X", "name": "EV Battery Pack Long Range", "base_cost": 8000.00, "category": "Components"},
|
|
33
|
+
{"id": 3, "sku": "MOT-HI-T", "name": "High Torque Motor", "base_cost": 1200.00, "category": "Components"},
|
|
34
|
+
# High Volume
|
|
35
|
+
{"id": 4, "sku": "BOLT-M5", "name": "Bolt M5 Stainless", "base_cost": 0.50, "category": "Hardware"},
|
|
36
|
+
{"id": 5, "sku": "WASH-M5", "name": "Washer M5", "base_cost": 0.10, "category": "Hardware"},
|
|
37
|
+
{"id": 6, "sku": "CAB-USB-C", "name": "USB-C Data Cable", "base_cost": 2.50, "category": "Electronics"},
|
|
38
|
+
{"id": 7, "sku": "BRK-SET", "name": "Brake Assembly Set", "base_cost": 350.00, "category": "Components"},
|
|
39
|
+
{"id": 8, "sku": "SNS-TEMP", "name": "Temperature Sensor Pack", "base_cost": 85.00, "category": "Electronics"},
|
|
40
|
+
{"id": 9, "sku": "PNL-AL", "name": "Aluminum Panel Sheet", "base_cost": 40.00, "category": "Materials"},
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
SUPPLIERS: List[Dict[str, Any]] = [
|
|
44
|
+
{"id": 1, "name": "Global Tech Components", "country": "Taiwan"},
|
|
45
|
+
{"id": 2, "name": "SteelCorp International", "country": "Germany"},
|
|
46
|
+
{"id": 3, "name": "ChemSafe Solutions", "country": "USA"},
|
|
47
|
+
{"id": 4, "name": "Nippon Metals", "country": "Japan"},
|
|
48
|
+
{"id": 5, "name": "Indus Industrial", "country": "India"},
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
DEPARTMENTS: List[Dict[str, Any]] = [
|
|
52
|
+
{"id": 1, "name": "Assembly"},
|
|
53
|
+
{"id": 2, "name": "Quality Assurance"},
|
|
54
|
+
{"id": 3, "name": "Logistics"},
|
|
55
|
+
{"id": 4, "name": "Maintenance"},
|
|
56
|
+
{"id": 5, "name": "Engineering"},
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
CUSTOMER_SEGMENTS: List[Dict[str, Any]] = [
|
|
60
|
+
{"id": 1, "name": "Enterprise"},
|
|
61
|
+
{"id": 2, "name": "SMB"},
|
|
62
|
+
{"id": 3, "name": "Retail"},
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
EMPLOYEE_ROLES: List[Dict[str, Any]] = [
|
|
66
|
+
{"id": 1, "title": "Operator", "department_id": 1},
|
|
67
|
+
{"id": 2, "title": "Line Supervisor", "department_id": 1},
|
|
68
|
+
{"id": 3, "title": "QA Analyst", "department_id": 2},
|
|
69
|
+
{"id": 4, "title": "Logistics Coordinator", "department_id": 3},
|
|
70
|
+
{"id": 5, "title": "Maintenance Technician", "department_id": 4},
|
|
71
|
+
{"id": 6, "title": "Process Engineer", "department_id": 5},
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
SUPPLIER_PRODUCTS: List[Dict[str, Any]] = [
|
|
75
|
+
{"supplier_id": 1, "product_id": 1},
|
|
76
|
+
{"supplier_id": 1, "product_id": 2},
|
|
77
|
+
{"supplier_id": 1, "product_id": 8},
|
|
78
|
+
{"supplier_id": 2, "product_id": 4},
|
|
79
|
+
{"supplier_id": 2, "product_id": 5},
|
|
80
|
+
{"supplier_id": 2, "product_id": 9},
|
|
81
|
+
{"supplier_id": 3, "product_id": 6},
|
|
82
|
+
{"supplier_id": 3, "product_id": 7},
|
|
83
|
+
{"supplier_id": 4, "product_id": 3},
|
|
84
|
+
{"supplier_id": 4, "product_id": 9},
|
|
85
|
+
{"supplier_id": 5, "product_id": 7},
|
|
86
|
+
{"supplier_id": 5, "product_id": 4},
|
|
87
|
+
]
|