Rifqi Hafizuddin commited on
Commit Β·
ade9c0c
1
Parent(s): 0721bb4
[NOTICKET] Add project repo documentation
Browse files- REPO_STATUS.md +303 -0
REPO_STATUS.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Data Eyond β Python Agentic Service: Current Status
|
| 2 |
+
|
| 3 |
+
**Audience:** teammates onboarding onto the Python repo (`Agentic-Service-Data-Eyond-Catalog`).
|
| 4 |
+
**Scope:** what the code does **right now** (branch `pr/3`, ticket KM-652). Describes current state only β no roadmap or to-dos.
|
| 5 |
+
**Snapshot date:** 2026-06-24.
|
| 6 |
+
|
| 7 |
+
> This file is grounded in the source, not the older design docs. Where the two
|
| 8 |
+
> disagree, the code wins β see [Β§11 Doc-vs-code](#11-where-the-older-docs-are-stale).
|
| 9 |
+
> `REPO_CONTEXT.md` / `ARCHITECTURE.md` are the original Phase-2 design docs and are
|
| 10 |
+
> stale on the router, joins, and the analysis/report stack.
|
| 11 |
+
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
## 1. The product in one paragraph
|
| 15 |
+
|
| 16 |
+
Data Eyond is an **"AI data scientist"** for business analytics, modelled on **CRISP-DM**
|
| 17 |
+
(Business Understanding β Data Understanding β Preparation β Modeling β Evaluation β
|
| 18 |
+
Deployment). It targets executives doing self-serve deep-dives and analysts offloading
|
| 19 |
+
routine work. A user defines a goal, connects data (DB or files), asks natural-language
|
| 20 |
+
analytical questions, and gets CRISP-DM-structured answers that can be exported as a
|
| 21 |
+
versioned **report**. The aim is "junior data scientist that hands back a decision-ready
|
| 22 |
+
deliverable," not "chatbot over a database."
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## 2. Three repos, one hard ownership rule
|
| 27 |
+
|
| 28 |
+
Request flow is **FE β Go β Python**. The FE never calls Python directly except for chat
|
| 29 |
+
streaming.
|
| 30 |
+
|
| 31 |
+
| Repo | Role | We edit? |
|
| 32 |
+
|---|---|---|
|
| 33 |
+
| **Python** β `Agentic-Service-Data-Eyond-Catalog` (this repo) | The agentic LLM service: router, gate, skills, slow analytical path, structured query engine, unstructured RAG, report generation, analysis-session state. FastAPI + async SQLAlchemy + LangChain + Azure GPT-4o. | **Yes β the only repo we edit.** |
|
| 34 |
+
| **Go** β `Orchestrator-Agent-Service` | Gateway / data plane: interview agent, auth/JWT, rooms, documents (Azure Blob + CSV/XLSXβParquet + embeddings), database_clients (Fernet creds), catalog ingestion, **all DB migrations**. | Reference only. |
|
| 35 |
+
| **FE** β `E2E-Frontend-Data-Eyond` | React/Vite SPA. Talks to Go for everything and to Python only for chat streaming. | Reference only. |
|
| 36 |
+
|
| 37 |
+
Shared infra: **Postgres** (app tables + `data_catalog` jsonb + PGVector `langchain_pg_embedding`), **Azure Blob**, and (Python-only) **Redis**.
|
| 38 |
+
|
| 39 |
+
---
|
| 40 |
+
|
| 41 |
+
## 3. Tech stack & how to run
|
| 42 |
+
|
| 43 |
+
- Python 3.12, FastAPI, uvicorn, sse-starlette
|
| 44 |
+
- Async SQLAlchemy 2.0 + asyncpg (Postgres); psycopg3 for the PGVector engine
|
| 45 |
+
- LangChain + langchain-openai (Azure OpenAI GPT-4o) + langchain-postgres (PGVector)
|
| 46 |
+
- Redis (response + retrieval cache), Azure Blob (uploads + Parquet)
|
| 47 |
+
- pandas / pyarrow, sqlglot, pydantic v2, structlog, slowapi, langfuse
|
| 48 |
+
- DB connectors: psycopg2, pymysql, pymssql, sqlalchemy-bigquery, snowflake-sqlalchemy
|
| 49 |
+
|
| 50 |
+
Run (Linux/Docker): `uv run --no-sync uvicorn main:app --host 0.0.0.0 --port 7860`
|
| 51 |
+
Run (Windows): `uv run --no-sync python run.py` (sets `WindowsSelectorEventLoopPolicy` for psycopg3 async β don't call uvicorn directly on Windows).
|
| 52 |
+
|
| 53 |
+
Tests live locally and are gitignored. Run with `./.venv/Scripts/python.exe -m pytest`.
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## 4. Chat request lifecycle
|
| 58 |
+
|
| 59 |
+
Entry: `POST /api/v1/chat/stream` (`src/api/v1/chat.py`) β `ChatHandler.handle(...)`
|
| 60 |
+
(`src/agents/chat_handler.py`). One shared `ChatHandler` per process keeps the Azure clients warm.
|
| 61 |
+
|
| 62 |
+
```
|
| 63 |
+
POST /chat/stream { user_id, room_id, message }
|
| 64 |
+
β (analysis_id == room_id β one session = one analysis = one chat room)
|
| 65 |
+
ββ Redis response-cache check (1h TTL, key chat:{room}:{user}:{message}) ββ hit β replay
|
| 66 |
+
ββ greeting/farewell short-circuit (_fast_intent, EN+ID) ββ hit β canned reply
|
| 67 |
+
ββ load last-10 history
|
| 68 |
+
ββ ChatHandler.handle:
|
| 69 |
+
1. classify β RouterDecision [1 GPT-4o call]
|
| 70 |
+
2. ensure analysis-state row (get-or-create, idempotent)
|
| 71 |
+
3. gate (if ENABLE_GATE): structured_flow + not validated β problem_statement
|
| 72 |
+
4. emit `intent` (internal; gates caching), then dispatch:
|
| 73 |
+
chat β ChatbotAgent β SSE
|
| 74 |
+
help β HelpAgent (state + history + readiness) β SSE
|
| 75 |
+
check β check_data/check_knowledge tool β rendered table [no LLM]
|
| 76 |
+
problem_statement β PS draft + validate β write state
|
| 77 |
+
unstructured_flow β DocumentRetriever (PGVector RAG) β ChatbotAgent β SSE
|
| 78 |
+
structured_flow β CatalogReader β (slow path | QueryService) β SSE
|
| 79 |
+
5. SSE events: intent (internal), sources, chunk, status, done | error
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
Only the `chat` intent is cached (stateless). Messages persist on `done`.
|
| 83 |
+
|
| 84 |
+
---
|
| 85 |
+
|
| 86 |
+
## 5. Report lifecycle
|
| 87 |
+
|
| 88 |
+
The report is a **dedicated API, not a chat route** (`src/api/v1/report.py`):
|
| 89 |
+
|
| 90 |
+
```
|
| 91 |
+
POST /report?analysis_id&user_id
|
| 92 |
+
ββ load analysis state; enforce the report FLOOR
|
| 93 |
+
β (validated goal + β₯1 substantive analyze_* success) β else 409
|
| 94 |
+
ββ ReportGenerator.generate (src/agents/report/generator.py):
|
| 95 |
+
β read persisted AnalysisRecords (list_for_analysis)
|
| 96 |
+
β deterministically assemble findings / caveats / open-questions /
|
| 97 |
+
β data-source appendix / CRISP-DM method appendix (copied verbatim)
|
| 98 |
+
β ONE LLM call β executive summary only (deterministic fallback on failure)
|
| 99 |
+
β render markdown
|
| 100 |
+
ββ ReportStore.save: advisory-locked version assignment β dedorch `reports`
|
| 101 |
+
ββ write report_id back onto analysis state
|
| 102 |
+
|
| 103 |
+
GET /report/{analysis_id} β list versions (oldest-first)
|
| 104 |
+
GET /report/{analysis_id}/{ver} β fetch one version
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
Two facts to internalise:
|
| 108 |
+
- **Records only exist on the slow path.** With `ENABLE_SLOW_PATH=false` (the default) no
|
| 109 |
+
records accumulate, so generation 409s β by design, not a bug.
|
| 110 |
+
- **dedorch `reports` stores markdown only.** Structured report fields are computed at
|
| 111 |
+
generation, rendered into `rendered_markdown`, and only the markdown is persisted; on
|
| 112 |
+
read-back the structured fields come back empty.
|
| 113 |
+
|
| 114 |
+
---
|
| 115 |
+
|
| 116 |
+
## 6. Feature list (what's built)
|
| 117 |
+
|
| 118 |
+
- **6-intent handler router** with history-aware query rewriting (EN/ID).
|
| 119 |
+
- **Deterministic gate** β `problem_validated` precondition for analytical queries.
|
| 120 |
+
- **Skills:** `help` (LLM, state-aware next-step guidance), `check` (no-LLM data/document inventory), `problem_statement` (LLM drafter + completeness validator).
|
| 121 |
+
- **Slow analytical path:** Planner β TaskRunner β Assembler (static plan, degrade-and-continue, 3 LLM calls fixed).
|
| 122 |
+
- **Structured query engine:** catalog-driven JSON IR β deterministic SQL/pandas compiler β read-only executor, with **single-level FK joins** (DB sources only).
|
| 123 |
+
- **Unstructured RAG** over PGVector.
|
| 124 |
+
- **Analytics tools:** 4 registered composite `analyze_*` (descriptive, aggregate, correlation, trend) + 4 data-access tools (check_data, check_knowledge, retrieve_data, retrieve_knowledge). Four further composites (comparison, contribution, profile, segment) exist in code but are **not registered** with the Planner.
|
| 125 |
+
- **Versioned report generation** from persisted records.
|
| 126 |
+
- **Analysis sessions:** data-first creation gate (β₯1 bound source), per-analysis data-source binding (#10).
|
| 127 |
+
- **Langfuse tracing** (PII-masked), **Redis caching**, **pooled DB engines** + speculative prewarm.
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## 7. API surface (this repo, all under `/api/v1`)
|
| 132 |
+
|
| 133 |
+
| Endpoint | Purpose | Caller |
|
| 134 |
+
|---|---|---|
|
| 135 |
+
| `POST /chat/stream` | Main chat SSE (router β gate β dispatch) | FE β Go β Python (the only FEβPython call today) |
|
| 136 |
+
| `DELETE /chat/cache` Β· `/chat/cache/room/{id}` Β· `/retrieval/cache/{user_id}` | Cache management | internal / ops |
|
| 137 |
+
| `POST /analysis/create` Β· `GET /analysis` Β· `GET /analysis/{id}` | Analysis-session CRUD (state + room + bindings created atomically) | intended FE β Go |
|
| 138 |
+
| `POST /report` Β· `GET /report/{id}` Β· `GET /report/{id}/{ver}` | Report generate / list / fetch | FE β Go (report button) |
|
| 139 |
+
| `GET /tools` | Slash-command catalog (static, cacheable) | Go caches it for the FE "/" menu |
|
| 140 |
+
| `users` Β· `room` Β· `document` Β· `db_client` Β· `data_catalog` routers | Phase-1 legacy; functionally migrated to Go | mostly dormant |
|
| 141 |
+
|
| 142 |
+
---
|
| 143 |
+
|
| 144 |
+
## 8. Data model
|
| 145 |
+
|
| 146 |
+
SQLAlchemy models in `src/db/postgres/models.py`. Created on startup by `init_db()`
|
| 147 |
+
unless `SKIP_INIT_DB=true`.
|
| 148 |
+
|
| 149 |
+
| Table | Shape | Written by | Read by |
|
| 150 |
+
|---|---|---|---|
|
| 151 |
+
| `users`, `rooms`, `chat_messages`, `message_sources` | base app | chat endpoint, Go | chat history |
|
| 152 |
+
| `documents`, `databases` | uploads + DB creds (Fernet-encrypted) | Go ingestion | executor cred resolution |
|
| 153 |
+
| `data_catalog` | per-user jsonb `Catalog` (Source β Table β Column) | Go ingestion / Python pipeline | CatalogReader, planner, tools |
|
| 154 |
+
| `langchain_pg_embedding` | PGVector document chunks | Go ingestion | DocumentRetriever |
|
| 155 |
+
| `analysis_records` | jsonb `AnalysisRecord`, one per slow-path run | slow path | ReportGenerator, report readiness |
|
| 156 |
+
| `analysis` *(dedorch)* | uuid id, `owner_id`, `problem_statement`, `problem_validated`, `report_id` | `/analysis/create`, state store | gate, Help, report |
|
| 157 |
+
| `reports` *(dedorch)* | uuid, `title` + markdown `content` + `version` | ReportStore | report API |
|
| 158 |
+
| `data_sources` *(dedorch)* | per-analysis binding; `reference_id` = catalog source_id | `/analysis/create` | structured-flow scoping, report appendix |
|
| 159 |
+
|
| 160 |
+
**Catalog shape** (the jsonb in `data_catalog`):
|
| 161 |
+
`Catalog β Source[ {source_id, source_type β schema|tabular|unstructured, name, location_ref} β Table[ {table_id, name, row_count, foreign_keys[]} β Column[ {column_id, name, data_type, nullable, pii_flag, sample_values|null, stats} ] ] ]`. PII columns have `sample_values: null` so real values never enter prompts.
|
| 162 |
+
|
| 163 |
+
**QueryIR shape** (`src/query/ir/models.py`):
|
| 164 |
+
`{ source_id, table_id, joins[], select[], filters[], group_by[], order_by[], limit }`.
|
| 165 |
+
Joins are single-level equi-joins to a related table **in the same source**, FK-backed,
|
| 166 |
+
**DB sources only**.
|
| 167 |
+
|
| 168 |
+
---
|
| 169 |
+
|
| 170 |
+
## 9. Subsystems (where the code lives)
|
| 171 |
+
|
| 172 |
+
### Router β `src/agents/orchestration.py`
|
| 173 |
+
One GPT-4o structured-output call β `RouterDecision{intent, rewritten_query, confidence}`,
|
| 174 |
+
`intent β {chat, help, problem_statement, check, unstructured_flow, structured_flow}`. It's a
|
| 175 |
+
*handler* classifier: `structured_flow` = slow path, `unstructured_flow` = fast RAG; the
|
| 176 |
+
data-modality mix on the slow path is the Planner's job. Prompt: `src/config/prompts/intent_router.md`.
|
| 177 |
+
|
| 178 |
+
### Gate β `src/agents/gate.py`
|
| 179 |
+
Pure function, no I/O. One rule: `structured_flow` + `not problem_validated` β redirect to
|
| 180 |
+
`problem_statement`; everything else passes. Fails **closed** (not-validated stub) on a missing
|
| 181 |
+
row / read error. `AnalysisState` is the locked contract (id, analysis_title, problem_statement,
|
| 182 |
+
problem_validated, owner_id, report_id, created_at, updated_at). Applied in `chat_handler.handle`
|
| 183 |
+
only when `ENABLE_GATE` and `analysis_id` are present.
|
| 184 |
+
|
| 185 |
+
### Skills β `src/agents/handlers/`
|
| 186 |
+
- `help.py` β LLM (streamed). A consistency guard derives the *allowed* actions from state
|
| 187 |
+
(mirrors the gate) and feeds them to the prompt, so Help can't suggest a report when the goal
|
| 188 |
+
isn't validated or there's nothing to report. Consumes a deterministic readiness signal.
|
| 189 |
+
- `check.py` β **no LLM.** Keyword cues route to `check_data`, `check_knowledge`, or both
|
| 190 |
+
(helicopter view, concurrent). Renders tool tables to markdown.
|
| 191 |
+
- `problem_statement.py` β LLM drafts `{problem_statement, objective, metric, missing[], feedback}`;
|
| 192 |
+
valid iff a statement exists and `missing` is empty (a bare data question stays incomplete). On a
|
| 193 |
+
valid draft, writes `problem_statement` + `problem_validated=True`.
|
| 194 |
+
|
| 195 |
+
### Slow path β `src/agents/slow_path/` + `src/agents/planner/`
|
| 196 |
+
- **Planner** (`planner/service.py`) β 1 LLM call β `TaskList` (DAG of tool-call chains). 8-check
|
| 197 |
+
validator with re-prompt retry (max 3). `BusinessContext` is a **stub** (`planner/business_context.py`),
|
| 198 |
+
which is why the slow path stays opt-in.
|
| 199 |
+
- **TaskRunner** (`slow_path/task_runner.py`) β deterministic, 0 LLM. Wave-based execution,
|
| 200 |
+
`${t<id>}` placeholder resolution (Pattern A), never-throw invocation, **degrade-and-continue**
|
| 201 |
+
(failed task β dependents skipped, independent branches run). No replanning.
|
| 202 |
+
- **Assembler** (`slow_path/assembler.py`) β 1 LLM call authoring only the narrative; code copies
|
| 203 |
+
the structured `results_snapshot` / `tasks_run` from the run state into the `AnalysisRecord`
|
| 204 |
+
(the report's source of truth).
|
| 205 |
+
|
| 206 |
+
Streaming + persistence: `chat_handler._run_slow_path` bridges per-stage progress to SSE `status`
|
| 207 |
+
events, prewarms the DB engine in parallel with planning, emits the answer, then persists the
|
| 208 |
+
record stamped with `user_id` + `analysis_id`.
|
| 209 |
+
|
| 210 |
+
### Structured query engine β `src/query/`
|
| 211 |
+
`QueryService.run` (`query/service.py`): plan β validate β retry(3) β dispatch β execute; **never
|
| 212 |
+
raises** (errors land in `QueryResult.error`). `IRValidator` (`query/ir/validator.py`) checks
|
| 213 |
+
source/table/column existence, op/agg whitelists, type compatibility, limit cap, and **FK-backed
|
| 214 |
+
joins** (DB only). `DbExecutor` (`query/executor/db.py`): SqlCompiler β sqlglot SELECT-only guard β
|
| 215 |
+
Fernet-decrypt creds (with owner check) β `asyncio.to_thread` (30s timeout) β pooled engine
|
| 216 |
+
(read-only + statement_timeout) β 10k row cap. Defense-in-depth: IR validation + compiler whitelist
|
| 217 |
+
+ sqlglot guard + read-only session + LIMIT/timeout.
|
| 218 |
+
|
| 219 |
+
### Data-source binding (#10) β `src/agents/binding_store.py`
|
| 220 |
+
At `/analysis/create`, chosen `data_source_ids` become `data_sources` rows. On a `structured_flow`
|
| 221 |
+
turn the catalog reader is wrapped so the Planner and the tools' re-reads see the same scoped
|
| 222 |
+
catalog. **Fail-open**: empty/disjoint binding β whole catalog.
|
| 223 |
+
|
| 224 |
+
### Tool layer β `src/tools/data_access.py`, `src/agents/planner/registry.py`
|
| 225 |
+
`DataAccessToolInvoker` implements the never-throw tool seam for the 4 data-access tools.
|
| 226 |
+
`retrieve_data` runs a pre-built IR (validate β dispatch β execute, skipping the planner) and
|
| 227 |
+
coerces `Decimal`β`float` β the Pattern A handoff the `analyze_*` tools consume. The planner
|
| 228 |
+
registry composes a local data-access spec stub (name-checked against `DATA_ACCESS_TOOLS`) with the
|
| 229 |
+
real `analytics_registry()`.
|
| 230 |
+
|
| 231 |
+
### Report β `src/agents/report/`
|
| 232 |
+
`generator.py` reads records, deterministically assembles structured fields, 1 LLM call for the
|
| 233 |
+
executive summary; `store.py` versions under an advisory lock and persists markdown to dedorch
|
| 234 |
+
`reports`; `readiness.py` defines the **report floor** (validated goal + β₯1 successful `analyze_*`)
|
| 235 |
+
shared by the report API and the Help readiness signal so the two can't disagree.
|
| 236 |
+
|
| 237 |
+
### Observability β Langfuse
|
| 238 |
+
The endpoint's `ChatHandler` runs with `enable_tracing=True`. One trace per request groups
|
| 239 |
+
router/planner/assembler/chatbot + tool spans. PII policy: router/planner unmasked (PII-safe
|
| 240 |
+
summaries); assembler/chatbot masked (see real rows); tool spans carry name + arg keys + row counts
|
| 241 |
+
only.
|
| 242 |
+
|
| 243 |
+
---
|
| 244 |
+
|
| 245 |
+
## 10. Feature flags
|
| 246 |
+
|
| 247 |
+
| Flag | Where | Default | Effect |
|
| 248 |
+
|---|---|---|---|
|
| 249 |
+
| `ENABLE_SLOW_PATH` | `settings.enable_slow_path` | **off** | Route `structured_flow` through Planner/TaskRunner/Assembler (vs single-query `QueryService`). Records persist only on the slow path β reports require this on. |
|
| 250 |
+
| `ENABLE_GATE` | `settings.enable_gate` | **off** | Apply the `problem_validated` redirect. Off because legacy rooms have no state row. |
|
| 251 |
+
| `SKIP_INIT_DB` | env, `main.py` | off | Skip `create_all` on startup β the dedorch cutover switch (Go owns dedorch migrations). |
|
| 252 |
+
| `enable_tracing` | hardcoded `True` in `chat.py` | on (endpoint) | Langfuse tracing. |
|
| 253 |
+
|
| 254 |
+
---
|
| 255 |
+
|
| 256 |
+
## 11. Where the older docs are stale
|
| 257 |
+
|
| 258 |
+
Trust the code. The original Phase-2 docs (`ARCHITECTURE.md`, `REPO_CONTEXT.md`) and the Go repo's
|
| 259 |
+
copies disagree with the current code on:
|
| 260 |
+
|
| 261 |
+
| Topic | Old docs | Current code |
|
| 262 |
+
|---|---|---|
|
| 263 |
+
| Router | 3-way `source_hint` (chat/unstructured/structured) | Flat **6-intent** `RouterDecision` |
|
| 264 |
+
| Joins in IR | "single-table only; deferred" | **Single-level FK-backed joins** (DB sources only) |
|
| 265 |
+
| Analysis / report / gate / slow path | "Phase 2 spine only" | All built and present |
|
| 266 |
+
| `analysis_id` | open question | resolved: **`analysis_id == room_id`** |
|
| 267 |
+
| Report source | (newer invariant) "from records, never chat history" | confirmed: generator reads `AnalysisRecord`s |
|
| 268 |
+
|
| 269 |
+
---
|
| 270 |
+
|
| 271 |
+
## 12. dedorch migration β current state
|
| 272 |
+
|
| 273 |
+
The Python DB is moving from `dataeyond` β **dedorch** (Go owns dedorch migrations; Python is
|
| 274 |
+
consumer-only). Current state:
|
| 275 |
+
|
| 276 |
+
- Base tables already match dedorch.
|
| 277 |
+
- The analysis-family models have been **renamed to dedorch** on `pr/3`: `analysis` (was
|
| 278 |
+
`analysis_states`, uuid ids), `data_sources` (was `analysis_data_sources`), `reports` (was
|
| 279 |
+
`analysis_reports`, flattened to title + markdown content + version).
|
| 280 |
+
- `analysis_records` (the slow-path structured output) has **no dedorch home** β it remains a
|
| 281 |
+
Python-owned jsonb table.
|
| 282 |
+
- The connection-string cutover (paired with `SKIP_INIT_DB`) is a coordinated step that has not
|
| 283 |
+
happened yet; Python still creates tables on startup until then.
|
| 284 |
+
|
| 285 |
+
The dedorch migrations themselves live outside the three checked-out repos (Harry owns them), so the
|
| 286 |
+
dedorch table shapes are asserted by the Python model docstrings, not visible in the Go repo here.
|
| 287 |
+
|
| 288 |
+
---
|
| 289 |
+
|
| 290 |
+
## 13. Conventions & gotchas
|
| 291 |
+
|
| 292 |
+
- **Two Postgres engines:** app engine + a separate PGVector engine (`prepared_statement_cache_size=0`)
|
| 293 |
+
because PGVector emits multi-statement strings asyncpg rejects.
|
| 294 |
+
- **Identifiers vs values:** identifiers come from the catalog and are inlined as quoted; filter
|
| 295 |
+
values are always parameterized.
|
| 296 |
+
- **Settings aliases:** `.env` uses double-underscore names (`azureai__api_key__4o`); `Settings`
|
| 297 |
+
exposes them as `azureai_api_key_4o`.
|
| 298 |
+
- **Never-throw seams** are pervasive (tool invoker, query service, executors, state/binding reads,
|
| 299 |
+
record persistence, report summary). Failures degrade into soft output rather than raising β good
|
| 300 |
+
for UX, but they can mask real breakage (e.g. a binding silently fail-opening to the full catalog).
|
| 301 |
+
- **Prompts** live in `src/config/prompts/*.md`. `chatbot_system.md` has `guardrails.md` appended so
|
| 302 |
+
guardrails win on conflict.
|
| 303 |
+
- **Tests** are gitignored (team decision) β run them locally.
|