Rifqi Hafizuddin commited on
Commit
d7dad60
Β·
2 Parent(s): 4c911cb8467ab8
Files changed (1) hide show
  1. PROGRESS.md +61 -8
PROGRESS.md CHANGED
@@ -2,7 +2,7 @@
2
 
3
  Persistent tracker mirroring the 42-item ownership table in `REPO_CONTEXT.md` "Team β€” division of work". Update as PRs land. Future Claude Code sessions read this to know what's already done.
4
 
5
- **Last updated**: 2026-06-10 (principal review findings logged + PR-DB1 latency work started)
6
  **Current open PR**: `pr/2` β€” active.
7
 
8
  ---
@@ -83,8 +83,10 @@ unchanged (flags default off).
83
  take a `data` `${t<id>}` placeholder from an upstream `query_structured`).
84
  - **Verified live E2E (2026-06-09):** real `query_structured` against a user's Neon
85
  Postgres β†’ `analyze_trend` β†’ Assembler. `analyze_contribution` surfaced a real tool
86
- bug (Decimal vs float in `decomposition.py`) β€” degrade-and-continue held; teammate's
87
- domain to fix. **Directive:** agent side does NOT modify `src/tools/` without confirmation.
 
 
88
 
89
  **Planner β€” realigned to the real tools (KM-626).** `registry.py::default_registry()`
90
  composes the real `analytics_registry()` + a local stub for the 4 data-access tools.
@@ -125,6 +127,44 @@ or keep the planner stub; 4o β†’ GPT-mini deployment swap; flip `enable_slow_pat
125
 
126
  ---
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  ## What just shipped (2026-06-08 β€” KM-626: slow-path agent layer)
129
 
130
  The rest of the slow path after the Planner (KM-567), from
@@ -276,7 +316,7 @@ per the architecture doc: Orchestrator slow-path expansion + TaskRunner + Assemb
276
  | # | Item | Owner | Status | Notes |
277
  |---|---|---|---|---|
278
  | 5 | DB introspector (`catalog/introspect/database.py`) | DB | `[x]` | PR1 β€” reuses Phase 1 `database_client_service`, `db_credential_encryption`, `db_pipeline_service.engine_scope`, `extractor.get_schema/profile_column/get_row_count`. PR2a wired FK extraction (was discarded before). |
279
- | 6 | Tabular introspector (`catalog/introspect/tabular.py`) | TAB | `[~]` | PR1-tab β€” downloads original blob (CSV/XLSX/Parquet), one Table per sheet (XLSX) or one Table (CSV/Parquet). `source_id = document_id`. `fetch_doc`/`fetch_blob` injectable for unit tests (no Settings). |
280
  | 7 | `BaseIntrospector` ABC (`catalog/introspect/base.py`) | B | `[x]` | Pre-existing; signature locked |
281
 
282
  ### Ingestion β€” shared catalog plumbing
@@ -324,8 +364,8 @@ per the architecture doc: Orchestrator slow-path expansion + TaskRunner + Assemb
324
 
325
  | # | Item | Status | Notes |
326
  |---|---|---|---|
327
- | 29 | Pandas compiler (`query/compiler/pandas.py`) | `[~]` | PR3-TAB β€” `CompiledPandas` dataclass; all 12 filter ops; all 6 aggs; group_by via `pd.concat` of Series; alias-aware order_by; `_like_to_regex` (`%`β†’`.*`, `_`β†’`.`); pure module-level helpers |
328
- | 30 | Tabular executor (`query/executor/tabular.py`) | `[~]` | PR3-TAB β€” `fetch_blob` injectable for tests; blob path: single-table β†’ `{uid}/{did}.parquet`, multi-table β†’ `{uid}/{did}__{table.name}.parquet`; `asyncio.to_thread`; 10k row hard cap; errors β†’ `QueryResult.error` |
329
  | 31 | Parquet upload/download wrapper | `[x]` | Moved `knowledge/parquet_service.py` β†’ `storage/parquet.py`. Updated 4 import sites: `pipeline/document_pipeline.py`, `knowledge/processing_service.py`, `query/executor/tabular.py`, `query/executors/tabular.py`. |
330
 
331
  ### Agents + chat
@@ -334,7 +374,20 @@ per the architecture doc: Orchestrator slow-path expansion + TaskRunner + Assemb
334
  |---|---|---|---|
335
  | 32 | Chatbot agent + prompt (`agents/chatbot.py`, `config/prompts/chatbot_system.md`) | `[x]` | PR7-bundle β€” `ChatbotAgent` (was `AnswerAgent`) streams tokens, accepts `QueryResult` or list[`DocumentChunk`] or neither. **Cleanup PR**: renamed `answer_agent.py` β†’ `chatbot.py`, `AnswerAgent` β†’ `ChatbotAgent`; Phase 1 `agents/chatbot.py` deleted. |
336
  | 33 | Guardrails prompt (`config/prompts/guardrails.md`) | `[x]` | PR7-bundle β€” appended to `chatbot_system.md` so guardrails take precedence in conflict. |
337
- | β€” | Chat handler / orchestrator (`agents/chat_handler.py`) | `[x]` | PR4-bundle β€” top-level Phase 2 orchestrator. Routes by `source_hint`: chat β†’ AnswerAgent direct; structured β†’ CatalogReader + QueryService; unstructured β†’ DocumentRetriever placeholder + AnswerAgent. Yields `intent` / `chunk` / `done` / `error` SSE-style events. Phase 1 chat.py NOT touched β€” cleanup PR rewires the API to call this. |
 
 
 
 
 
 
 
 
 
 
 
 
 
338
 
339
  ### API surface
340
 
@@ -351,7 +404,7 @@ per the architecture doc: Orchestrator slow-path expansion + TaskRunner + Assemb
351
  | # | Item | Owner | Status | Notes |
352
  |---|---|---|---|---|
353
  | 38 | DB compiler golden tests (`tests/query/compiler/test_sql.py`) | DB | `[x]` | PR3-DB β€” 36 tests across all whitelisted ops, identifier quoting, agg / count_distinct / count(*), order_by alias resolution, parameter sequencing, error paths. Pure-Python, no LLM, no DB. |
354
- | 39 | Pandas compiler golden tests (`tests/unit/query/compiler/test_pandas_compiler.py`) | TAB | `[~]` | PR3-TAB β€” 43 tests: all 12 filter ops, all 6 aggs, group_by, order_by, limit, aliases, empty DataFrame, error paths. `test_tabular_executor.py` adds 12 more (blob name resolution + happy path + error paths). |
355
  | 40 | IR validator tests (`tests/query/ir/test_validator.py`) | B | `[x]` | PR1 β€” 19 tests, all rules covered |
356
  | β€” | PII detector tests (`tests/catalog/test_pii_detector.py`) | B | `[x]` | PR1 β€” 26 tests (parametrized) |
357
  | β€” | Catalog validator tests (`tests/catalog/test_validator.py`) | B | `[x]` | PR1 β€” 5 tests |
 
2
 
3
  Persistent tracker mirroring the 42-item ownership table in `REPO_CONTEXT.md` "Team β€” division of work". Update as PRs land. Future Claude Code sessions read this to know what's already done.
4
 
5
+ **Last updated**: 2026-06-10 (tool layer complete + hardening/DRY + Langfuse tracing + gated slow-path wiring)
6
  **Current open PR**: `pr/2` β€” active.
7
 
8
  ---
 
83
  take a `data` `${t<id>}` placeholder from an upstream `query_structured`).
84
  - **Verified live E2E (2026-06-09):** real `query_structured` against a user's Neon
85
  Postgres β†’ `analyze_trend` β†’ Assembler. `analyze_contribution` surfaced a real tool
86
+ bug (Decimal vs float in `decomposition.py`) β€” degrade-and-continue held; **now fixed
87
+ by the tool owner** (`_coerce_decimals` in `invoker._materialize`, KM-630 / commit
88
+ 1195870), so the whole `analyze_*` family is covered in one place. **Directive:** agent
89
+ side does NOT modify `src/tools/` without confirmation.
90
 
91
  **Planner β€” realigned to the real tools (KM-626).** `registry.py::default_registry()`
92
  composes the real `analytics_registry()` + a local stub for the 4 data-access tools.
 
127
 
128
  ---
129
 
130
+ ## What just shipped (2026-06-10 β€” TAB: tool-layer hardening + DRY)
131
+
132
+ Owner-side companion to the agent block above. After the live E2E surfaced real-data
133
+ edge cases, the tool layer got a round of correctness hardening. All in TAB-owned paths
134
+ (`src/tools/`, `src/catalog/`); no agent-side or API change.
135
+
136
+ **JSON-safety across the `analyze_*` family.** Real DB rows carry scalar types that
137
+ don't survive the jsonb / SSE round-trip:
138
+ - `[KM-630] coerce DB Decimal β†’ float` (commit 1195870) β€” `_coerce_decimals` in
139
+ `invoker._materialize` converts object-columns holding `decimal.Decimal` (asyncpg
140
+ returns NUMERIC as `Decimal`) to `float64` before any compute runs. Fixes the
141
+ `float + Decimal` TypeError in `decomposition.analyze_contribution` **and** the whole
142
+ family in one seam β€” only touches columns that actually contain a `Decimal`.
143
+ - `[KM-624] non-JSON-safe scalars in mode & top_value` (commit 6981ed3) β€” normalize
144
+ numpy / non-native scalars so descriptive + top-value outputs serialize cleanly.
145
+
146
+ **Planner↔Tools registry alignment + Timestamp keys** (commit 4bb7623, `fix(tools)`):
147
+ - `registry.py` β€” `analyze_descriptive.required` corrected `["data"]` β†’ `["data",
148
+ "column_ids"]` to match the compute signature (`column_ids` has no default). Prevents
149
+ the Planner from emitting a call that's missing a required arg. `analyze_profile` stays
150
+ `["data"]` (its `column_ids` defaults to `None`).
151
+ - `aggregation._clean` β€” group-by over a datetime column produced `pd.Timestamp` group
152
+ keys that aren't JSON-safe; now normalized to `.isoformat()` alongside the existing
153
+ numpy `.item()` branch.
154
+
155
+ **DRY: single `SAMPLE_LIMIT` constant** (commit 6d46ba5, `[NOTICKET] refactor(catalog)`):
156
+ - One source of truth in `catalog/introspect/base.py` (`SAMPLE_LIMIT = 3`, down from 5 β€”
157
+ token cost: sample values feed the planner prompt). Both introspection paths import it:
158
+ `catalog/introspect/tabular.py` and `pipeline/db_pipeline/extractor.py` (which dropped
159
+ its own local `= 3`). Dependency direction is pipeline→catalog (no circular import).
160
+ Stale test `test_sample_values_capped_at_five` updated to assert the real cap (3).
161
+
162
+ **Audit result:** Planner↔Tools arg alignment swept end-to-end β€” 7/8 `analyze_*` tools
163
+ already matched; the 1 mismatch (`analyze_descriptive`) is the fix above. Pattern A holds
164
+ across all of them.
165
+
166
+ ---
167
+
168
  ## What just shipped (2026-06-08 β€” KM-626: slow-path agent layer)
169
 
170
  The rest of the slow path after the Planner (KM-567), from
 
316
  | # | Item | Owner | Status | Notes |
317
  |---|---|---|---|---|
318
  | 5 | DB introspector (`catalog/introspect/database.py`) | DB | `[x]` | PR1 β€” reuses Phase 1 `database_client_service`, `db_credential_encryption`, `db_pipeline_service.engine_scope`, `extractor.get_schema/profile_column/get_row_count`. PR2a wired FK extraction (was discarded before). |
319
+ | 6 | Tabular introspector (`catalog/introspect/tabular.py`) | TAB | `[x]` | PR1-tab β€” downloads original blob (CSV/XLSX/Parquet), one Table per sheet (XLSX) or one Table (CSV/Parquet). `source_id = document_id`. `fetch_doc`/`fetch_blob` injectable for unit tests (no Settings). **2026-06-10**: sample cap now imports the shared `SAMPLE_LIMIT` (=3) from `catalog/introspect/base.py` β€” single source of truth across the tabular + DB introspection paths (commit 6d46ba5). |
320
  | 7 | `BaseIntrospector` ABC (`catalog/introspect/base.py`) | B | `[x]` | Pre-existing; signature locked |
321
 
322
  ### Ingestion β€” shared catalog plumbing
 
364
 
365
  | # | Item | Status | Notes |
366
  |---|---|---|---|
367
+ | 29 | Pandas compiler (`query/compiler/pandas.py`) | `[x]` | PR3-TAB β€” `CompiledPandas` dataclass; all 12 filter ops; all 6 aggs; group_by via `pd.concat` of Series; alias-aware order_by; `_like_to_regex` (`%`β†’`.*`, `_`β†’`.`); pure module-level helpers. (`polars` for large files still deferred β€” see Planned dependencies.) |
368
+ | 30 | Tabular executor (`query/executor/tabular.py`) | `[x]` | PR3-TAB β€” `fetch_blob` injectable for tests; blob path: single-table β†’ `{uid}/{did}.parquet`, multi-table β†’ `{uid}/{did}__{table.name}.parquet`; `asyncio.to_thread`; 10k row hard cap; errors β†’ `QueryResult.error`. Dispatcher routes to it by `source_type`. |
369
  | 31 | Parquet upload/download wrapper | `[x]` | Moved `knowledge/parquet_service.py` β†’ `storage/parquet.py`. Updated 4 import sites: `pipeline/document_pipeline.py`, `knowledge/processing_service.py`, `query/executor/tabular.py`, `query/executors/tabular.py`. |
370
 
371
  ### Agents + chat
 
374
  |---|---|---|---|
375
  | 32 | Chatbot agent + prompt (`agents/chatbot.py`, `config/prompts/chatbot_system.md`) | `[x]` | PR7-bundle β€” `ChatbotAgent` (was `AnswerAgent`) streams tokens, accepts `QueryResult` or list[`DocumentChunk`] or neither. **Cleanup PR**: renamed `answer_agent.py` β†’ `chatbot.py`, `AnswerAgent` β†’ `ChatbotAgent`; Phase 1 `agents/chatbot.py` deleted. |
376
  | 33 | Guardrails prompt (`config/prompts/guardrails.md`) | `[x]` | PR7-bundle β€” appended to `chatbot_system.md` so guardrails take precedence in conflict. |
377
+ | — | Chat handler / orchestrator (`agents/chat_handler.py`) | `[x]` | PR4-bundle — top-level Phase 2 orchestrator. Routes by `source_hint`: chat → AnswerAgent direct; structured → CatalogReader + QueryService; unstructured → DocumentRetriever placeholder + AnswerAgent. Yields `intent` / `chunk` / `done` / `error` SSE-style events. Phase 1 chat.py NOT touched — cleanup PR rewires the API to call this. **2026-06-09**: gained the gated `structured→slow` branch (`enable_slow_path=False`) + `enable_tracing` (KM-626/631). |
378
+
379
+ ### Tools β€” slow-path "Tools" component (TAB)
380
+
381
+ New scope after the original 42-item table; added as the tool layer landed (KM-608/624–631). All TAB-owned (`src/tools/`), all never-throw (Β§8.4).
382
+
383
+ | # | Item | Owner | Status | Notes |
384
+ |---|---|---|---|---|
385
+ | β€” | Analytics compute fns (`tools/analytics/`) | TAB | `[x]` | KM-608/624/625 β€” 8 **composite** `analyze_*` fns (descriptive, aggregate, comparison, contribution, profile, correlation, segment, trend) + prompt-style DESCRIPTIONs. Pure pandas, no I/O. JSON-safe outputs (numpy/Decimal/Timestamp normalized β€” KM-624 + commit 4bb7623). |
386
+ | β€” | Tool contracts (`tools/contracts.py`) | TAB | `[x]` | KM-627 β€” canonical `ToolSpec` / `ToolRegistry` / `ToolOutput`. `agents/planner/contracts.py` re-exports them (+ keeps the lead's `BusinessContext` stub). |
387
+ | β€” | Analytics registry (`tools/registry.py`) | TAB | `[x]` | KM-628 β€” `analytics_registry()`. `analyze_descriptive.required` = `["data","column_ids"]` (aligned to compute signature, commit 4bb7623). |
388
+ | β€” | Invoker layer (`tools/invoker.py`) | TAB | `[x]` | KM-629 β€” `AnalyticsToolInvoker` (Pattern A: `analyze_*` take a `data` `${t<id>}` placeholder from upstream `query_structured`; `_materialize` β†’ DataFrame, `_coerce_decimals` covers the whole family) + `CompositeToolInvoker` (routes data-access vs analytics by name). |
389
+ | β€” | Data-access tools (`tools/data_access.py`) | TAB | `[x]` | KM-630 β€” `DataAccessToolInvoker`: `list_sources` / `describe_source` / `query_structured` / `retrieve_documents`. Per-request DI (`user_id` + `CatalogReader`). `query_structured` calls `IRValidator` + `ExecutorDispatcher` (planner skipped β€” IR pre-built by the agent Planner). |
390
+ | β€” | Tool tests (`tests/unit/tools/`) | TAB | `[x]` | analytics + data-access + invoker tests (gitignored). Incl. regression `test_decimal_columns_coerced_for_analyze_contribution`. |
391
 
392
  ### API surface
393
 
 
404
  | # | Item | Owner | Status | Notes |
405
  |---|---|---|---|---|
406
  | 38 | DB compiler golden tests (`tests/query/compiler/test_sql.py`) | DB | `[x]` | PR3-DB β€” 36 tests across all whitelisted ops, identifier quoting, agg / count_distinct / count(*), order_by alias resolution, parameter sequencing, error paths. Pure-Python, no LLM, no DB. |
407
+ | 39 | Pandas compiler golden tests (`tests/unit/query/compiler/test_pandas_compiler.py`) | TAB | `[x]` | PR3-TAB β€” 43 tests: all 12 filter ops, all 6 aggs, group_by, order_by, limit, aliases, empty DataFrame, error paths. `test_tabular_executor.py` adds 12 more (blob name resolution + happy path + error paths). |
408
  | 40 | IR validator tests (`tests/query/ir/test_validator.py`) | B | `[x]` | PR1 β€” 19 tests, all rules covered |
409
  | β€” | PII detector tests (`tests/catalog/test_pii_detector.py`) | B | `[x]` | PR1 β€” 26 tests (parametrized) |
410
  | β€” | Catalog validator tests (`tests/catalog/test_validator.py`) | B | `[x]` | PR1 β€” 5 tests |