Automotive Analyst
A bring-your-own-key text-to-SQL agent for the synthetic manufacturing warehouse. The browser calls Claude, OpenAI, or Gemini directly, while the backend serves schema context and safely runs read-only PostgreSQL with visible guardrails.
Evidence
Screenshots from the live app showing the BYOK panel, natural-language sample questions, and the guarded execution workflow.




Problem
Manufacturing data warehouses answer valuable questions, but most users cannot translate an operations question into correct SQL quickly. A plain-English analyst can help, but generated SQL against a database needs stronger safety boundaries than a typical chat demo.
The project needed to let a visitor ask questions over the synthetic factory warehouse while keeping the LLM key out of the backend and preventing writes, catalog access, chained statements, or runaway queries.
Users and decisions
Manufacturing analysts, supervisors, and engineers are the intended users. They can ask operational questions in plain language, inspect the generated read-only SQL and results, and decide where downtime, quality, or reliability patterns warrant follow-up.
The app is a portfolio companion to the Manufacturing Intelligence Platform. It uses the same synthetic warehouse read-only and is not connected to proprietary or employer data.
The browser holds the key; the backend holds the guardrail
Automotive Analyst uses a bring-your-own-key model. The visitor selects Claude, OpenAI, or Gemini, and the browser sends the schema-grounded prompt directly to that provider. The generated SQL is then posted to the backend for validation and execution.
The backend holds no LLM secret. Its job is deliberately narrower: serve schema context, validate SQL through an allow-list guardrail layer, execute inside a read-only transaction using a read-only database role, and return the answer with the exact SQL shown.
Architecture
Data flow
The browser loads sample questions and schema context from the API. When a visitor asks a question, the browser calls the chosen model provider directly and receives SQL text.
The backend accepts the question and SQL, validates the SQL, injects a default limit when needed, executes safely against the synthetic warehouse, and returns columns, rows, a visualization hint, the guardrail verdict, and the final SQL.
Tools used
Key features
- Bring-your-own-key provider panel with session-only key storage.
- Client-side provider abstraction for Claude, OpenAI, and Gemini.
- Schema context and few-shot examples served by the API for grounded SQL generation.
- Fail-closed SQL guardrails for single-statement, read-only, allow-listed queries.
- Dedicated read-only database role, read-only transaction, statement timeout, rate limits, and request size caps.
- Exact SQL returned with each answer so the user can audit what was run.
- Self-correction path when a generated query fails database execution.
Safety model
The project treats client-supplied SQL as untrusted by default. The app layer rejects chained statements, write operations, admin functions, catalog access, unknown tables, comment obfuscation, and unsafe query shapes before anything reaches the database.
The database then provides a second boundary: execution happens through a dedicated read-only role inside a read-only transaction with a timeout. Even if the app guardrail missed something, the database role is still not allowed to write.
Methodology
The agent reads the same synthetic manufacturing warehouse as the factory dashboard. Sample questions cover station downtime, OEE, robot fault trends, monthly defect movement, yield comparison, crew repair speed, and defect origin versus detection.
The guardrail test suite covers attack cases including statement chaining, destructive SQL, catalog probes, file reads, information schema access, COPY-style exfiltration, comment hiding, and false-positive keyword traps.
What the guardrail actually withstands
The project demonstrates how to expose an AI analytics interface without asking the backend to hold model keys or trust generated SQL blindly.
Evidence for
43 / 43Adversarial SQL attacks blocked, across quoting, statement chaining, write and DDL, catalog access, info-leak function calls, indirect relation access, comment evasion, and payload smuggling.Evidence for
16 / 16Legitimate analytical queries accepted. A guardrail that rejects ordinary work gets switched off, so false positives are scored as failures alongside bypasses.Evidence against
1 bypassFound by writing the suite, in the guardrail's own allow-list. Quoted identifiers were unreadable by the relation parser, soSELECT * FROM "users" passed. Fixed, and it is now the first case in the corpus.The evaluation found a bypass in my own guardrail
This project does not claim the model writes correct SQL. It claims that nothing the model writes can do damage. That is a testable claim, and it needed a test, so I wrote an adversarial corpus of 43 attacks against the validator and 16 ordinary analytical queries that must still be allowed through.
Writing it found a real hole. The relation parser matched only bare identifiers, using a pattern restricted to letters and underscores. Against a quoted identifier it therefore matched nothing at all. An empty match set meant no unknown tables were found, and the allow-list passed by default rather than failing closed. Postgres accepts quoted identifiers, so SELECT * FROM "users" read a table that was never on the list, and so did "information_schema"."tables". Eight of nine quoting variants got through. The allow-list was not rejecting quoted names; it never saw them.
Severity, stated accurately. The database role is read-only and the query runs inside a read-only transaction, so this was unauthorised reading rather than writing, and the defense-in-depth design held. But the allow-list is the specific control that is supposed to stop arbitrary table reads, and it was not doing that. A design being layered is not a reason to under-report a failure in one of the layers.
The fix, and why it is more than a wider regex. The parser now reads quoted and bare identifiers in both the schema and object position, and folds them to a comparable form. More importantly the validator now fails closed: every FROM and JOIN has to be accounted for as either a named relation or a parenthesised subquery, and a query whose sources cannot all be classified is refused rather than passed on an empty match. That is what catches quoting styles I did not think of, including backticks and brackets.
The same pass fixed three false positives. Keyword scanning ran across string literals, so SELECT 'drop' AS label was rejected. A semicolon inside a literal read as statement chaining. A column named version tripped the admin-function denylist. These matter because a guardrail that blocks ordinary work gets disabled, so the suite scores false positives as failures alongside bypasses.
The corpus lives at backend/tests/test_guardrails_adversarial.py and runs in CI. The quoted-identifier case is the first entry in it.
Limitations
This is a portfolio demonstration over synthetic data. The user must provide a model key, and answer quality depends on the provider model, schema prompt, and generated SQL.
A production version would add authentication, durable audit logs, richer semantic modeling, stricter cost controls, saved approved queries, and more extensive evaluation of generated SQL accuracy.
What I would improve next
I would add query fingerprints, an approval layer for common executive questions, expanded SQL accuracy evaluations, richer chart recommendations, and a deeper data dictionary so non-technical users can ask better follow-up questions.