# Chat With Your Data

A natural-language interface to the BudgetDB warehouse — the kind of tool where a VP asks
*"which vendors are we paying the most for relative to how many teams actually use them?"*
and gets a real, numbers-backed answer, without opening SQL or waiting on an analyst.

This is the direct continuation of [BudgetDB](../budgetdb_full_warehouse.sql): that project
built the trustworthy data model (raw → dimensional → fact → scenario engine → QA); this
project is the conversational layer on top of it.

## How it works

1. Claude is given the warehouse's real schema — table names, column meanings, and notes on
   which table to prefer for which kind of question (e.g. use `fact_cost_monthly` for "total
   cost" questions rather than summing the source tables by hand).
2. Claude has exactly one tool: `run_sql_query`. It decides what SQL answers the question,
   calls the tool, gets real rows back, and can run follow-up queries if the first result
   doesn't fully answer it — this is a real agentic loop, not a single prompt-to-SQL shot.
3. Once it has enough, it answers in plain English, citing the numbers it actually queried.

## Safety model

This is a finance tool, so "the AI can only read, never write" isn't a nice-to-have:

- **Every generated query is checked against an allowlist** before execution — must start
  with `SELECT`/`WITH`, must not contain `INSERT`/`UPDATE`/`DELETE`/`DROP`/`ALTER`/`CREATE`/
  `TRUNCATE`/`GRANT`/`REVOKE`/`ATTACH`/`PRAGMA` anywhere, and no stacked statements.
- **The database connection itself is opened read-only** (SQLite's `mode=ro`), so even a
  query that somehow passed the allowlist would still be rejected by the driver. Two
  independent layers on purpose — in production, the second layer is a dedicated read-only
  Postgres role (`GRANT SELECT`, nothing else), not just an SQLite connection flag.
- **Every query is printed before it runs**, so there's always a visible audit trail of
  exactly what was asked of the database, not a black box.
- The allowlist is deliberately over-cautious: a query like
  `WHERE vendor_name = 'DROP'` gets rejected too, because it contains the literal word
  `DROP`. That's a known, accepted tradeoff — false positives are fine here, false
  negatives on a write-blocking check are not.

Both `is_safe_select()` and the actual read-only execution path are covered by
`test_safety.py`, which runs against the real seeded database, not mocks — see the test
output for exact proof this works (blocked `DELETE`, allowed `SELECT`, real rows returned).

## Try it

```bash
pip3 install anthropic
export ANTHROPIC_API_KEY=sk-ant-...
python3 seed_demo_db.py                 # builds the demo warehouse (anonymized sample data)
python3 test_safety.py                  # proves the safety layer works, no API key needed
python3 chat_with_data.py "Which vendors are we paying the most for relative to how many teams actually use them?"
python3 chat_with_data.py               # interactive mode
```

## What's real vs. what's next

**Actually built and tested in this environment:** the safety allowlist, the read-only
database execution path, the seeded demo warehouse, and the full tool-calling request/
response loop (written against the Anthropic Python SDK's documented pattern). **Not
tested here:** the live Claude API call itself — no API key was available in the build
environment. If you're running this, that round trip is the one thing worth confirming
first.

**Path from this prototype to production:**
1. Swap `sqlite3` for a `psycopg2`/`psycopg` connection to the real Postgres warehouse,
   using a dedicated read-only role — same safety model, real data.
2. Add a lightweight web UI (Streamlit or a small Next.js app, consistent with the stack
   already used for Ghost AI) instead of a terminal loop, so non-technical leadership
   doesn't need a terminal.
3. Log every question + generated SQL + answer to a table for auditability — a finance
   tool that leadership queries for savings decisions should leave a paper trail.
4. Consider a confirmation step before answering questions whose SQL touches payroll/
   salary columns specifically, even though it's already read-only — some numbers warrant
   an extra "are you sure you want this surfaced in chat" gate regardless of write risk.
