/fix report not working
#18
by rhbt6767 - opened
- DEV_PLAN.md +6 -3
- REPO_STATUS.md +46 -9
- src/agents/report/store.py +1 -0
- src/db/postgres/models.py +4 -0
DEV_PLAN.md
CHANGED
|
@@ -215,7 +215,7 @@ Status legend: β¬ not started Β· π in progress Β· β
done Β· β blocked Β·
|
|
| 215 |
| 19 | Decide report author display-name source (`users.fullname` vs Go-passed name) | Sofhia | β
| Done 2026-06-25. `AnalysisReport.user_name`; `generator` renders `user_name or user_id`; `api/v1/report.py::_resolve_user_name` reads `users.fullname` never-throw (fallback `user_id`). Decided: resolve in Python (unblocked); swap to Go-passed name later if preferred |
|
| 216 |
| 20 | **Help handoff:** update `handlers/help.py` + `help.md` β drop the `problem_validated` tier + `define_problem_statement` action (the skill it points at is gone as of #1) | Sofhia | β
| Done 2026-06-25. `help.py`: actions = `ask_analysis_question` (always) + `generate_report` (if ready); renders objective/business_questions (getattr-tolerant). `help.md` v1βv2: 3 tiers, no `/problem_statement`, `/generate report`β`/report`. Local test_help updated β 11 pass |
|
| 217 |
| 21 | Rename `analysis_records` β **`report_inputs`** (table, ORM `ReportInputRow`, store `*ReportInputStore`) | Rifqi | β
| Done 2026-06-26. `sed` rename across 9 files; Pydantic `AnalysisRecord` kept; columns stay String (pure rename β uuid+FK is the #22 Harry schema). Name `report_inputs` (purpose; avoids Langfuse/`analyses_messages` clash). Write scope = one row per slow-path run. Suite **284 passed** |
|
| 218 |
-
| 22 | Finalize `report_inputs` schema β hand to Harry for the dedorch migration | Rifqi β Harry |
|
| 219 |
| 23 | Report markdown formatting: tables, **bold**, *italic*, horizontal separators | Sofhia | β
| Done 2026-06-25. Added `---` separators between header + each section in `_render_markdown`. Tables (EDA) / bold (method labels) / italic (meta + citations) already emitted. Relaxed `report_summary.md` to allow inline `**bold**`/`*italic*` for emphasis (kept no-headings/no-bullets so it doesn't duplicate the section structure / Key Findings). Compile + ruff clean |
|
| 220 |
| 24 | Clarify report input contract: records table (+ `last_report` for edit mode?) | Rifqi/Sofhia β Harry | β¬ new | Edit-mode input left open at the checkpoint |
|
| 221 |
| 25 | Migrate Python chat path to Go `analyses_messages` (+ `analyses`) | Rifqi β Harry | β
| Done 2026-07-02. Read path already on `analyses_messages` (commit `0066161`). This change makes Python **read-only**: removed the `save_messages` calls from `/api/v2/chat/stream` so **Go is the sole writer** β fixes the double-write both Go+Python were producing. `load_history` still reads `analyses_messages`. v1 `/chat/stream` is unwired so left untouched |
|
|
@@ -223,11 +223,14 @@ Status legend: β¬ not started Β· π in progress Β· β
done Β· β blocked Β·
|
|
| 223 |
| 27 | **Images (DEFERRED):** image table (id, analysis_id, msg/report ref, order) + originals in a bucket | β | βΈοΈ | Maintenance-heavy; parked |
|
| 224 |
| 28 | **UI research** (FE): new-analysis form, knowledge menu (user vs analysis level), report artifacts + version selector | Team | β¬ new | No dedicated UI person; interview + old analysis UI removed |
|
| 225 |
| 29 | **LLM env quad rename `__4o` β `__54m`** + set the four `azureai__*__54m` secrets on the HF Space | Rifqi | π | Code done 2026-07-14 (pr/17): settings quad + 9 call sites + 2 eval runners renamed; deployment is `gpt-5.4-mini`. Suite **381 passed, 2 failed (both pre-existing), 7 skipped** β same as baseline; ruff clean on touched files; `import main` 0. **Hard rename β no `__4o` fallback**, so HF fails loudly on the first LLM call until the four `__54m` secrets are set. Root cause it fixes: HF silently ran GPT-4o while local ran 5.4-mini β same question, same catalog, planner hallucinated catalog ids on HF only. **Action: set HF secrets, redeploy, re-run the failing question.** Local `.env` still carries the now-dead `__4o` keys β safe to delete |
|
|
|
|
|
|
|
|
|
|
| 226 |
|
| 227 |
## 5. Critical path & sequencing
|
| 228 |
|
| 229 |
-
- **Critical path:** #22 (send Harry the `report_inputs` schema). HF deploy (#13) for the playground. (#4 β
, #21 β
; Harry's #3 no longer blocks us β Python is getattr-tolerant.)
|
| 230 |
-
- **Parallelizable now:** #
|
| 231 |
- **Harry-blocked / coordinated:** #3 (now π, blocks #4), #7 (Go endpoint), #18 (Go state ownership), #24 (contract). **#25 = chat-path migration to `analyses_messages` β a cutover blocker.**
|
| 232 |
- **Demo gate (playground, #13):** deploy the remove-`problem_statement` work to HF β slow path (#15 β
)
|
| 233 |
and the report path are verified locally, and #16 is resolved (#22 hands Harry the schema). **Keep it
|
|
|
|
| 215 |
| 19 | Decide report author display-name source (`users.fullname` vs Go-passed name) | Sofhia | β
| Done 2026-06-25. `AnalysisReport.user_name`; `generator` renders `user_name or user_id`; `api/v1/report.py::_resolve_user_name` reads `users.fullname` never-throw (fallback `user_id`). Decided: resolve in Python (unblocked); swap to Go-passed name later if preferred |
|
| 216 |
| 20 | **Help handoff:** update `handlers/help.py` + `help.md` β drop the `problem_validated` tier + `define_problem_statement` action (the skill it points at is gone as of #1) | Sofhia | β
| Done 2026-06-25. `help.py`: actions = `ask_analysis_question` (always) + `generate_report` (if ready); renders objective/business_questions (getattr-tolerant). `help.md` v1βv2: 3 tiers, no `/problem_statement`, `/generate report`β`/report`. Local test_help updated β 11 pass |
|
| 217 |
| 21 | Rename `analysis_records` β **`report_inputs`** (table, ORM `ReportInputRow`, store `*ReportInputStore`) | Rifqi | β
| Done 2026-06-26. `sed` rename across 9 files; Pydantic `AnalysisRecord` kept; columns stay String (pure rename β uuid+FK is the #22 Harry schema). Name `report_inputs` (purpose; avoids Langfuse/`analyses_messages` clash). Write scope = one row per slow-path run. Suite **284 passed** |
|
| 218 |
+
| 22 | Finalize `report_inputs` schema β hand to Harry for the dedorch migration | Rifqi β Harry | β
| **DDL ready** (uuid `id`/`analysis_id` + FKβ`analyses(id)`; `user_id`/`plan_id` text; `data` jsonb = serialized `AnalysisRecord`, shape documented). dedorch has empty `analysis_records` β rename. Resolves #16. ~~**Action: send Harry the DDL + `data` shape**~~ **RESOLVED 2026-07-22:** Harry's `0001_create_core_schema.sql` now carries `report_inputs` with exactly this shape β FK to `analyses(id)` included. Python's `ReportInputRow` diffed against the live Neon table: **column-for-column identical, no drift.** The two remaining un-migrated Python tables (`message_traceability`, `message_charts`) split out as β #32 |
|
| 219 |
| 23 | Report markdown formatting: tables, **bold**, *italic*, horizontal separators | Sofhia | β
| Done 2026-06-25. Added `---` separators between header + each section in `_render_markdown`. Tables (EDA) / bold (method labels) / italic (meta + citations) already emitted. Relaxed `report_summary.md` to allow inline `**bold**`/`*italic*` for emphasis (kept no-headings/no-bullets so it doesn't duplicate the section structure / Key Findings). Compile + ruff clean |
|
| 220 |
| 24 | Clarify report input contract: records table (+ `last_report` for edit mode?) | Rifqi/Sofhia β Harry | β¬ new | Edit-mode input left open at the checkpoint |
|
| 221 |
| 25 | Migrate Python chat path to Go `analyses_messages` (+ `analyses`) | Rifqi β Harry | β
| Done 2026-07-02. Read path already on `analyses_messages` (commit `0066161`). This change makes Python **read-only**: removed the `save_messages` calls from `/api/v2/chat/stream` so **Go is the sole writer** β fixes the double-write both Go+Python were producing. `load_history` still reads `analyses_messages`. v1 `/chat/stream` is unwired so left untouched |
|
|
|
|
| 223 |
| 27 | **Images (DEFERRED):** image table (id, analysis_id, msg/report ref, order) + originals in a bucket | β | βΈοΈ | Maintenance-heavy; parked |
|
| 224 |
| 28 | **UI research** (FE): new-analysis form, knowledge menu (user vs analysis level), report artifacts + version selector | Team | β¬ new | No dedicated UI person; interview + old analysis UI removed |
|
| 225 |
| 29 | **LLM env quad rename `__4o` β `__54m`** + set the four `azureai__*__54m` secrets on the HF Space | Rifqi | π | Code done 2026-07-14 (pr/17): settings quad + 9 call sites + 2 eval runners renamed; deployment is `gpt-5.4-mini`. Suite **381 passed, 2 failed (both pre-existing), 7 skipped** β same as baseline; ruff clean on touched files; `import main` 0. **Hard rename β no `__4o` fallback**, so HF fails loudly on the first LLM call until the four `__54m` secrets are set. Root cause it fixes: HF silently ran GPT-4o while local ran 5.4-mini β same question, same catalog, planner hallucinated catalog ids on HF only. **Action: set HF secrets, redeploy, re-run the failing question.** Local `.env` still carries the now-dead `__4o` keys β safe to delete |
|
| 226 |
+
| 30 | **Neon `reports.user_id` NOT NULL** β reconcile the ORM + `ReportStore` write | Rifqi | β
| Done 2026-07-22 (pr/18, commit `13f74e1`). `ReportStore.save` never wrote `user_id`; on Neon that column is `text NOT NULL` β **every `POST /api/v1/tools/report` 500'd** with `NotNullViolationError`. Fix: `models.py` adds `user_id = Column(Text)` (**nullable** on purpose β Python matches the loosest deployment shape, Β§7D getattr-tolerance) + `store.py` writes `report.user_id`, which the endpoint already required (`user_id: str = Query(...)`) and threaded via `generator.generate`. Live-tested by Rifqi β **201**. Suite 381 passed / 2 pre-existing failures / 7 skipped; `import main` 0; ruff unchanged (3 pre-existing E501 in `models.py`, same count on HEAD). **Correction 2026-07-22 (after pulling Go `dd37b38`):** the commit message and the first version of this row said the column was *not* in Go migrations `0001β0004`. **That was wrong** β `0001_create_core_schema.sql` has declared `reports.user_id TEXT NOT NULL` since the schema was written. Real root cause β #31 |
|
| 227 |
+
| 31 | **Go migration set is not convergent** β fresh vs migrated dedorch DBs get different NOT NULL constraints | Rifqi β Harry | β¬ new | Root cause of #30, verified in the Go source 2026-07-22. `0001_create_core_schema.sql` creates `reports.user_id TEXT NOT NULL` and `analyses_messages.user_id TEXT NOT NULL`; `0002_cleanup_legacy_schema.sql` (L95, L92) and `0004_replace_chat_with_analysis_scope.sql` (L61, L57) retrofit the same two columns onto pre-existing DBs as **nullable** `ALTER TABLE β¦ ADD COLUMN IF NOT EXISTS user_id TEXT`. Because `CREATE TABLE IF NOT EXISTS` no-ops on an existing table, **the same migration set yields two different schemas** β the old dedorch DB got nullable (hiding Python's missing write for months), Neon got NOT NULL. **Ask Harry:** make the retrofit converge (backfill + `ALTER COLUMN β¦ SET NOT NULL`, or add the constraint in a new migration) so every instance matches `0001`. Until then, `information_schema` on the target instance β not the migration files β is the only reliable schema source (REPO_STATUS Β§13). **Action: Rifqi raises with Harry** |
|
| 228 |
+
| 32 | **`message_traceability` + `message_charts` are in no Go migration** β hand the DDL to Harry | Rifqi β Harry | β¬ new | Found by the 2026-07-22 drift scan. Both tables exist on Neon **only because they were created by hand** (2026-07-06 / 2026-07-13); neither appears in `0001β0006`. Any newly provisioned dedorch instance will be missing them β traceability flush and chart persist fail (both never-throw, so they degrade **silently** β no 500 like #30 to make it visible). DDL for `message_charts` is in `SPINE_V2_PLAN.md` Β§4.4. This is the surviving half of #22. **Action: Rifqi sends Harry both DDL blocks** |
|
| 229 |
|
| 230 |
## 5. Critical path & sequencing
|
| 231 |
|
| 232 |
+
- **Critical path:** ~~#22 (send Harry the `report_inputs` schema)~~ **β
resolved 2026-07-22** β now **#32** (`message_traceability` + `message_charts` DDL to Harry) and **#31** (non-convergent migration set). HF deploy (#13) for the playground. (#4 β
, #21 β
; Harry's #3 no longer blocks us β Python is getattr-tolerant.)
|
| 233 |
+
- **Parallelizable now:** #31 + #32 (both are Harry handoffs). (#4 β
, #11 β
, #22 β
done.)
|
| 234 |
- **Harry-blocked / coordinated:** #3 (now π, blocks #4), #7 (Go endpoint), #18 (Go state ownership), #24 (contract). **#25 = chat-path migration to `analyses_messages` β a cutover blocker.**
|
| 235 |
- **Demo gate (playground, #13):** deploy the remove-`problem_statement` work to HF β slow path (#15 β
)
|
| 236 |
and the report path are verified locally, and #16 is resolved (#22 hands Harry the schema). **Keep it
|
REPO_STATUS.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
|
| 3 |
**Audience:** teammates onboarding onto the Python repo (`Agentic-Service-Data-Eyond-Catalog`).
|
| 4 |
**Scope:** what the code does **right now** (branch `pr/4`, ticket KM-652). Describes current state only β no roadmap or to-dos.
|
| 5 |
-
**Snapshot date:** 2026-06-25. **Data-layer reconcile 2026-07-01:** Β§8/Β§12 updated β dedorch cutover done, `data_catalog` model reconciled. **Query-path fix 2026-07-02:** Β§8/Β§13 β dedorch catalogs ship no FKs β Python infers them (`fk_inference.py`); shared-Fernet-key gotcha documented. **Agent-quality fixes 2026-07-08 (pr/13):** from the scoped live-test review β the planner gains an explicit **infeasible** outcome (`TaskList.infeasible_reason` β deterministic EN/ID data-gap reply via `refusals.data_gap_message`; no more force-mapping absent measures like `pa` AS "revenue"), the IR validator rejects bare selects under `group_by` (self-corrects via the planner retry), `analyze_trend` handles integer year/month columns (was collapsing every row into one 1970-01 bucket), planner few-shots add top-N (Example G) + infeasible (Example H), numeric catalog `sample_values` are base64-decoded at read (`catalog/sample_decode.py` β stopgap for Go's byte-marshaling; primary fix is Go-side), traceability no longer emits null source rows for failed retrievals, and `check_data` hides `-1` row counts. **Report v2 + analyze_merge planner support 2026-07-09 (pr/13):** Sofia's `analyze_merge` tool (8abf635, KM-703) is now planner-supported (`_validate_data_source` guards `data_right`, two-retrieveβmerge few-shot Example I, planner.md "Two measures per entity" rule); the report gains per-business-question answers (`bq_answers` β drafted by the SAME single LLM call, index-based record refs, deterministic fallback unchanged), "Attempted, Unresolved" + "Excluded Analyses" sections (failed runs are no longer silently dropped), evidence tables copied from `results_snapshot` (table-kind outputs, β€3/record β€10 rows β€8 cols, `check_*` skipped), normalized caveat dedupe with caps (12/10), and single-language output via `detect_reply_language`; the report surface adds `GET /tools/report/{analysis_id}/records` (curation list), `GET β¦/readiness` (FE delta guard), and `exclude_record_ids` on POST β see API_CONTRACT_BE_PYTHON.md. **Report compaction 2026-07-09 (pr/13):** the rendered markdown drops the "Notes & Limitations", "Attempted, Unresolved", and "How This Was Analyzed" sections (team decision β compact report; render blocks commented out in `report/generator.py`, not deleted). The JSON body keeps `caveats`/`open_questions`/`unresolved`/`method_steps` and the curation/records endpoints are unchanged. **Traceability `data_used` layer 2026-07-13 (pr/15):** `GET /api/v1/traceability` gains a resolved, user-facing `data_used[]` block (one per `retrieve_data` call) β real source/table/column names, joins, plain-language filters, and result columns split into read-from-data vs `computed` (with `formula`, e.g. `total_revenue` = `SUM(line_total)`, so an alias is never shown as a real column). Ids are kept but **machine-only (FE must not render)**. Also adds `tool_calls[].summary`, and `sources[]` now carry `source_name` + every table touched. Deterministic catalog resolution (new `src/traceability/resolve.py`), no LLM, never-throw; catalog threaded to the scratchpad at the slow-path composition root. Additive/non-breaking; contract + `TRACEABILITY_FE_HANDOFF.md` updated. **Spine v2 W2+W1 2026-07-13 (pr/16):** Β§6/Β§7/Β§8/Β§9/Β§12 β `render_chart` tool lands (first of the `render_*` family: deterministic Plotly-JSON `dataeyond.chart.v1` envelope, hand-built, **no plotly dependency**; planner-selected only on an explicit chart ask, EN/ID) + Python-owned `message_charts` store + `GET /api/v1/charts` (FE fetches on `done`, same pattern as traceability; empty list = valid 200); planner gains a named **recipe table** + viz few-shots (Example J tail, Example K viz-infeasible) + validator Check 10 (`render_chart.data` must reference a table-producing task); and the slow path gains the **S1a quality checkpoint** (`slow_path/checkpoint.py`, 0 LLM, never-throw, between runner and assembler: CK1 all-failed β deterministic honest-failure answer with **no** assembler call, CK2 empty retrieve + downstream, CK3 10k-cap truncation, CK4 single trend bucket, CK5 all-null column, CK6 chart-spec sanity; flags render as an "Execution assessment" block in the assembler input and every flag logs `repair_candidate` β the S1b evidence base). Design + handoff doc: `SPINE_V2_PLAN.md`. **LLM model + env rename 2026-07-14 (pr/17):** Β§2/Β§3/Β§9/Β§13 β the generation LLM is now **Azure GPT-5.4-mini** (deployment `gpt-5.4-mini`), not GPT-4o, and the settings quad is renamed `azureai__*__4o` β **`azureai__*__54m`** across all 9 LLM call sites. **Hard rename, no `__4o` fallback** β an environment that still sets only `__4o` resolves to empty strings and fails on the first LLM call, by design: the silent-wrong-model drift is exactly how HF stayed on GPT-4o while local ran 5.4-mini (identical question, identical catalog, divergent planner output β HF hallucinated catalog ids, local planned correctly). Deploying requires all four `__54m` vars set in the HF Space secrets. **Cross-repo update 2026-06-29:** Β§2/Β§8/Β§11/Β§12 re-verified against
|
| 6 |
the **Go source** (`Orchestrator-Agent-Service`), not its docs. The Go service has moved well past its
|
| 7 |
own (uncommitted, stale) design docs: it now hosts the **dedorch SQL migrations** in-repo and a full
|
| 8 |
**`/api/v1/analyses` + `/api/v1/skills`** REST surface. Go does **not** call Python yet β those skills
|
|
@@ -187,7 +187,7 @@ unless `SKIP_INIT_DB=true`.
|
|
| 187 |
| `langchain_pg_embedding` | PGVector document chunks | Go ingestion | DocumentRetriever |
|
| 188 |
| `report_inputs` *(was `analysis_records`)* | jsonb `AnalysisRecord`, one per slow-path run; **Python-owned** | slow path | ReportGenerator, report readiness |
|
| 189 |
| `analyses` *(dedorch, plural)* | uuid `id`, `user_id`, `analysis_title`, `objective`, `business_questions` jsonb, `status` (active\|inactive), `data_bind`(+`data_bind_version`), `report_id`, `report_collection` β **defined by Go migrations**; `problem_statement`/`problem_validated`/`owner_id` already **dropped** there (`0003`/`0004`) | Go `/api/v1/analyses`; Python state store | gate (no-op), Help, report |
|
| 190 |
-
| `reports` *(dedorch)* | uuid, `analysis_id`, `user_id`, `title` + markdown `content` + `version` (UNIQUE per analysis) | Go + Python ReportStore | report API |
|
| 191 |
| `data_sources` *(dedorch, Go-owned)* | per-analysis binding table. **Python no longer reads or writes it** β bindings live in Go's `analyses.data_bind`, which Go materializes into the analysis-scope `data_catalog` row; Python scopes off that row. The table exists (Go migration) but Python is fully decoupled β do **not** drop it manually | Go migration | β (unused by Python) |
|
| 192 |
| `analyses_messages` *(dedorch)* | the analysis chat room (`role β user\|ai`); replaces deprecated `rooms`/`chat_messages` | Go `/analyses/{id}/messages` | Python chat path **not yet migrated here** (Β§12) |
|
| 193 |
| `message_traceability` *(Python-owned)* | one jsonb `TraceabilityPayload` per assistant turn (PK `message_id`); flushed before `done` | chat pipeline (KM-691) | `GET /api/v1/traceability` |
|
|
@@ -298,7 +298,9 @@ validator's Check 10 forces its `data` to reference a table-producing task.
|
|
| 298 |
### Report β `src/agents/report/`
|
| 299 |
`generator.py` reads records, deterministically assembles structured fields, 1 LLM call for the
|
| 300 |
executive summary; `store.py` versions under an advisory lock and persists markdown to dedorch
|
| 301 |
-
`reports`
|
|
|
|
|
|
|
| 302 |
2026-07-14, `render_chart` β a chart-only session is substantive; the `problem_validated`
|
| 303 |
precondition was dropped 2026-06-25) shared by the report API and the Help readiness signal so the
|
| 304 |
two can't disagree. **2026-07-14:** the report embeds charts β `_collect_charts` copies
|
|
@@ -359,10 +361,16 @@ Python is consumer-only). State **re-verified against the Go source 2026-06-29**
|
|
| 359 |
**dropped** `problem_statement`/`problem_validated`/`owner_id`. Migration `0004` renames the legacy
|
| 360 |
`rooms`/`chat_messages`/`interview_*` tables to `zdeprecated_*`.
|
| 361 |
- **`report_inputs`** (the slow-path structured output, formerly `analysis_records`) stays
|
| 362 |
-
**Python-owned**
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
- **Connection-string cutover DONE (2026-07-01).** Python's `postgres_connstring` now points at
|
| 367 |
**dedorch** and reads the Go-migrated tables directly. Every ORM model Python reads (`analyses`,
|
| 368 |
`analyses_messages`, `data_catalog`) has been reconciled to its dedorch shape.
|
|
@@ -370,8 +378,11 @@ Python is consumer-only). State **re-verified against the Go source 2026-06-29**
|
|
| 370 |
DDL (`ALTER TABLE rooms β¦`, index creation) fails on Go-owned tables
|
| 371 |
(`InsufficientPrivilegeError: must be owner of table rooms`). Skipping is safe β Go migration `0001`
|
| 372 |
already provides the `vector` extension + the langchain FTS index. Set `SKIP_INIT_DB=false` (.env or
|
| 373 |
-
env) only for a local Python-owned DB. `report_inputs` is not in any Go migration yet (#22) β create
|
| 374 |
-
it in dedorch before enabling the slow path, else report/slow-path writes fail (chat path unaffected).
|
|
|
|
|
|
|
|
|
|
| 375 |
|
| 376 |
**β οΈ Integration gap (verified β the big one).** Go's `/api/v1/analyses` and `/api/v1/skills`
|
| 377 |
(`help` / `report`) are **placeholders that return dummy data** β the `SendMessage` / `GenerateReport`
|
|
@@ -413,6 +424,32 @@ records-based report; floor: β₯1 `analyze_*` success). Wiring Go β Python is
|
|
| 413 |
degrades it into an honest "data not available" answer, so it masquerades as a data problem.
|
| 414 |
Tell-apart: `BlobNotFound` + `location_ref` starting `object_storage://` β env gap; set
|
| 415 |
`storage_provider=supabase_s3` + the five `supabase_s3_*` values (match Go's data plane).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 416 |
- **Never-throw seams** are pervasive (tool invoker, query service, executors, state/catalog reads,
|
| 417 |
record persistence, report summary). Failures degrade into soft output rather than raising β good
|
| 418 |
for UX, but they can mask real breakage (e.g. a missing analysis-scope catalog silently falling
|
|
|
|
| 2 |
|
| 3 |
**Audience:** teammates onboarding onto the Python repo (`Agentic-Service-Data-Eyond-Catalog`).
|
| 4 |
**Scope:** what the code does **right now** (branch `pr/4`, ticket KM-652). Describes current state only β no roadmap or to-dos.
|
| 5 |
+
**Snapshot date:** 2026-06-25. **Data-layer reconcile 2026-07-01:** Β§8/Β§12 updated β dedorch cutover done, `data_catalog` model reconciled. **Query-path fix 2026-07-02:** Β§8/Β§13 β dedorch catalogs ship no FKs β Python infers them (`fk_inference.py`); shared-Fernet-key gotcha documented. **Agent-quality fixes 2026-07-08 (pr/13):** from the scoped live-test review β the planner gains an explicit **infeasible** outcome (`TaskList.infeasible_reason` β deterministic EN/ID data-gap reply via `refusals.data_gap_message`; no more force-mapping absent measures like `pa` AS "revenue"), the IR validator rejects bare selects under `group_by` (self-corrects via the planner retry), `analyze_trend` handles integer year/month columns (was collapsing every row into one 1970-01 bucket), planner few-shots add top-N (Example G) + infeasible (Example H), numeric catalog `sample_values` are base64-decoded at read (`catalog/sample_decode.py` β stopgap for Go's byte-marshaling; primary fix is Go-side), traceability no longer emits null source rows for failed retrievals, and `check_data` hides `-1` row counts. **Report v2 + analyze_merge planner support 2026-07-09 (pr/13):** Sofia's `analyze_merge` tool (8abf635, KM-703) is now planner-supported (`_validate_data_source` guards `data_right`, two-retrieveβmerge few-shot Example I, planner.md "Two measures per entity" rule); the report gains per-business-question answers (`bq_answers` β drafted by the SAME single LLM call, index-based record refs, deterministic fallback unchanged), "Attempted, Unresolved" + "Excluded Analyses" sections (failed runs are no longer silently dropped), evidence tables copied from `results_snapshot` (table-kind outputs, β€3/record β€10 rows β€8 cols, `check_*` skipped), normalized caveat dedupe with caps (12/10), and single-language output via `detect_reply_language`; the report surface adds `GET /tools/report/{analysis_id}/records` (curation list), `GET β¦/readiness` (FE delta guard), and `exclude_record_ids` on POST β see API_CONTRACT_BE_PYTHON.md. **Report compaction 2026-07-09 (pr/13):** the rendered markdown drops the "Notes & Limitations", "Attempted, Unresolved", and "How This Was Analyzed" sections (team decision β compact report; render blocks commented out in `report/generator.py`, not deleted). The JSON body keeps `caveats`/`open_questions`/`unresolved`/`method_steps` and the curation/records endpoints are unchanged. **Traceability `data_used` layer 2026-07-13 (pr/15):** `GET /api/v1/traceability` gains a resolved, user-facing `data_used[]` block (one per `retrieve_data` call) β real source/table/column names, joins, plain-language filters, and result columns split into read-from-data vs `computed` (with `formula`, e.g. `total_revenue` = `SUM(line_total)`, so an alias is never shown as a real column). Ids are kept but **machine-only (FE must not render)**. Also adds `tool_calls[].summary`, and `sources[]` now carry `source_name` + every table touched. Deterministic catalog resolution (new `src/traceability/resolve.py`), no LLM, never-throw; catalog threaded to the scratchpad at the slow-path composition root. Additive/non-breaking; contract + `TRACEABILITY_FE_HANDOFF.md` updated. **Spine v2 W2+W1 2026-07-13 (pr/16):** Β§6/Β§7/Β§8/Β§9/Β§12 β `render_chart` tool lands (first of the `render_*` family: deterministic Plotly-JSON `dataeyond.chart.v1` envelope, hand-built, **no plotly dependency**; planner-selected only on an explicit chart ask, EN/ID) + Python-owned `message_charts` store + `GET /api/v1/charts` (FE fetches on `done`, same pattern as traceability; empty list = valid 200); planner gains a named **recipe table** + viz few-shots (Example J tail, Example K viz-infeasible) + validator Check 10 (`render_chart.data` must reference a table-producing task); and the slow path gains the **S1a quality checkpoint** (`slow_path/checkpoint.py`, 0 LLM, never-throw, between runner and assembler: CK1 all-failed β deterministic honest-failure answer with **no** assembler call, CK2 empty retrieve + downstream, CK3 10k-cap truncation, CK4 single trend bucket, CK5 all-null column, CK6 chart-spec sanity; flags render as an "Execution assessment" block in the assembler input and every flag logs `repair_candidate` β the S1b evidence base). Design + handoff doc: `SPINE_V2_PLAN.md`. **LLM model + env rename 2026-07-14 (pr/17):** Β§2/Β§3/Β§9/Β§13 β the generation LLM is now **Azure GPT-5.4-mini** (deployment `gpt-5.4-mini`), not GPT-4o, and the settings quad is renamed `azureai__*__4o` β **`azureai__*__54m`** across all 9 LLM call sites. **Hard rename, no `__4o` fallback** β an environment that still sets only `__4o` resolves to empty strings and fails on the first LLM call, by design: the silent-wrong-model drift is exactly how HF stayed on GPT-4o while local ran 5.4-mini (identical question, identical catalog, divergent planner output β HF hallucinated catalog ids, local planned correctly). Deploying requires all four `__54m` vars set in the HF Space secrets. **`reports.user_id` fix 2026-07-22 (pr/18):** Β§8/Β§9/Β§12/Β§13 β `ReportStore.save` never wrote `reports.user_id`, and on the new **Neon** dedorch instance that column is `text NOT NULL`, so **every `POST /api/v1/tools/report` 500'd** (`NotNullViolationError`; report persistence deliberately re-raises rather than degrading). The ORM gains `user_id` (declared **nullable** so Python tolerates every deployment shape) and the store writes `report.user_id`, which the endpoint already required as a query param and threaded through the generator. Live-verified against `information_schema` + a green 201. **Root cause (verified against the Go source after pulling `dd37b38`):** the column is *not* new β `0001_create_core_schema.sql` has declared it `NOT NULL` all along, but `0002`/`0004` retrofit it onto pre-existing DBs as a **nullable** `ADD COLUMN IF NOT EXISTS`. The old DB therefore accepted the column-less INSERT and the bug stayed latent; a fresh DB enforces it. Generalized as a Β§13 gotcha β **Go's migration set is not convergent, so fresh and migrated dedorch DBs differ in NOT NULL constraints** and the migrations alone can't tell you which shape you have. A full ORM-vs-live drift scan the same day found all five Python-written tables clean; the remaining drift is on read-only or unwired paths. **Cross-repo update 2026-06-29:** Β§2/Β§8/Β§11/Β§12 re-verified against
|
| 6 |
the **Go source** (`Orchestrator-Agent-Service`), not its docs. The Go service has moved well past its
|
| 7 |
own (uncommitted, stale) design docs: it now hosts the **dedorch SQL migrations** in-repo and a full
|
| 8 |
**`/api/v1/analyses` + `/api/v1/skills`** REST surface. Go does **not** call Python yet β those skills
|
|
|
|
| 187 |
| `langchain_pg_embedding` | PGVector document chunks | Go ingestion | DocumentRetriever |
|
| 188 |
| `report_inputs` *(was `analysis_records`)* | jsonb `AnalysisRecord`, one per slow-path run; **Python-owned** | slow path | ReportGenerator, report readiness |
|
| 189 |
| `analyses` *(dedorch, plural)* | uuid `id`, `user_id`, `analysis_title`, `objective`, `business_questions` jsonb, `status` (active\|inactive), `data_bind`(+`data_bind_version`), `report_id`, `report_collection` β **defined by Go migrations**; `problem_statement`/`problem_validated`/`owner_id` already **dropped** there (`0003`/`0004`) | Go `/api/v1/analyses`; Python state store | gate (no-op), Help, report |
|
| 190 |
+
| `reports` *(dedorch)* | uuid, `analysis_id`, **`user_id` text NOT NULL**, `title` + markdown `content` + `version` (UNIQUE per analysis). **2026-07-22:** `AnalysisReportRow` gained `user_id` and `ReportStore.save` now writes it β before that the INSERT omitted the column and every `POST /tools/report` 500'd on the new Neon instance (Β§13) | Go + Python ReportStore | report API |
|
| 191 |
| `data_sources` *(dedorch, Go-owned)* | per-analysis binding table. **Python no longer reads or writes it** β bindings live in Go's `analyses.data_bind`, which Go materializes into the analysis-scope `data_catalog` row; Python scopes off that row. The table exists (Go migration) but Python is fully decoupled β do **not** drop it manually | Go migration | β (unused by Python) |
|
| 192 |
| `analyses_messages` *(dedorch)* | the analysis chat room (`role β user\|ai`); replaces deprecated `rooms`/`chat_messages` | Go `/analyses/{id}/messages` | Python chat path **not yet migrated here** (Β§12) |
|
| 193 |
| `message_traceability` *(Python-owned)* | one jsonb `TraceabilityPayload` per assistant turn (PK `message_id`); flushed before `done` | chat pipeline (KM-691) | `GET /api/v1/traceability` |
|
|
|
|
| 298 |
### Report β `src/agents/report/`
|
| 299 |
`generator.py` reads records, deterministically assembles structured fields, 1 LLM call for the
|
| 300 |
executive summary; `store.py` versions under an advisory lock and persists markdown to dedorch
|
| 301 |
+
`reports` (**2026-07-22:** the row now carries `user_id`, threaded endpoint β generator β store β
|
| 302 |
+
`POST /tools/report` takes it as a required query param, so it is never null on the live path);
|
| 303 |
+
`readiness.py` defines the **report floor** (β₯1 successful `analyze_*` **or**, since
|
| 304 |
2026-07-14, `render_chart` β a chart-only session is substantive; the `problem_validated`
|
| 305 |
precondition was dropped 2026-06-25) shared by the report API and the Help readiness signal so the
|
| 306 |
two can't disagree. **2026-07-14:** the report embeds charts β `_collect_charts` copies
|
|
|
|
| 361 |
**dropped** `problem_statement`/`problem_validated`/`owner_id`. Migration `0004` renames the legacy
|
| 362 |
`rooms`/`chat_messages`/`interview_*` tables to `zdeprecated_*`.
|
| 363 |
- **`report_inputs`** (the slow-path structured output, formerly `analysis_records`) stays
|
| 364 |
+
**Python-owned** in the sense that Python is its only writer β but **β
2026-07-22: Harry's
|
| 365 |
+
migration now creates it.** `0001_create_core_schema.sql` carries the table (uuid `id`, `analysis_id`
|
| 366 |
+
uuid nullable **with the FK to `analyses(id)`**, `user_id`/`plan_id` text NOT NULL, `data` jsonb
|
| 367 |
+
NOT NULL, `created_at` timestamptz), exactly the DDL handed over in #22 β which closes #22.
|
| 368 |
+
Python's `ReportInputRow` was diffed against the live Neon table the same day: **column-for-column
|
| 369 |
+
identical**, no drift.
|
| 370 |
+
**`message_traceability`** (created manually 2026-07-06) and **`message_charts`** (created manually
|
| 371 |
+
2026-07-13, DDL in `SPINE_V2_PLAN.md` Β§4.4) are **still in no Go migration** β they exist on Neon
|
| 372 |
+
because they were created by hand, so a future fresh dedorch DB will not have them. Harry's
|
| 373 |
+
migration handoff for those two remains the open item (DEV_PLAN #32).
|
| 374 |
- **Connection-string cutover DONE (2026-07-01).** Python's `postgres_connstring` now points at
|
| 375 |
**dedorch** and reads the Go-migrated tables directly. Every ORM model Python reads (`analyses`,
|
| 376 |
`analyses_messages`, `data_catalog`) has been reconciled to its dedorch shape.
|
|
|
|
| 378 |
DDL (`ALTER TABLE rooms β¦`, index creation) fails on Go-owned tables
|
| 379 |
(`InsufficientPrivilegeError: must be owner of table rooms`). Skipping is safe β Go migration `0001`
|
| 380 |
already provides the `vector` extension + the langchain FTS index. Set `SKIP_INIT_DB=false` (.env or
|
| 381 |
+
env) only for a local Python-owned DB. ~~`report_inputs` is not in any Go migration yet (#22) β create
|
| 382 |
+
it in dedorch before enabling the slow path, else report/slow-path writes fail (chat path unaffected).~~
|
| 383 |
+
**RESOLVED 2026-07-22:** `report_inputs` ships in Go's `0001` (above), so a fresh dedorch DB has it.
|
| 384 |
+
The same is **not** true of `message_traceability`/`message_charts` β those still need the manual DDL
|
| 385 |
+
on any newly provisioned instance (DEV_PLAN #32).
|
| 386 |
|
| 387 |
**β οΈ Integration gap (verified β the big one).** Go's `/api/v1/analyses` and `/api/v1/skills`
|
| 388 |
(`help` / `report`) are **placeholders that return dummy data** β the `SendMessage` / `GenerateReport`
|
|
|
|
| 424 |
degrades it into an honest "data not available" answer, so it masquerades as a data problem.
|
| 425 |
Tell-apart: `BlobNotFound` + `location_ref` starting `object_storage://` β env gap; set
|
| 426 |
`storage_provider=supabase_s3` + the five `supabase_s3_*` values (match Go's data plane).
|
| 427 |
+
- **Fresh vs migrated dedorch DBs have different NOT NULL constraints (gotcha, found 2026-07-22 β
|
| 428 |
+
verified against the Go source, corrects an earlier note in this file that said the column was
|
| 429 |
+
missing from the migrations).** Go's migration set is not convergent: `0001_create_core_schema.sql`
|
| 430 |
+
declares `reports.user_id TEXT NOT NULL` **and** `analyses_messages.user_id TEXT NOT NULL` in its
|
| 431 |
+
`CREATE TABLE`s, while `0002`/`0004` retrofit the same columns onto pre-existing databases as
|
| 432 |
+
`ALTER TABLE β¦ ADD COLUMN IF NOT EXISTS user_id TEXT` β **nullable**. So a DB where the table
|
| 433 |
+
already existed ends up nullable (the `CREATE TABLE IF NOT EXISTS` no-ops), and a DB created fresh
|
| 434 |
+
from `0001` ends up NOT NULL. **Same migrations, two schemas.**
|
| 435 |
+
This is what the 2026-07-22 report outage actually was: `ReportStore.save` had *never* written
|
| 436 |
+
`user_id`, and the old dedorch DB's nullable column silently accepted it. The Neon instance was
|
| 437 |
+
provisioned fresh, so `NOT NULL` bit immediately β every `POST /api/v1/tools/report` 500'd with
|
| 438 |
+
`Report persistence failed: β¦ NotNullViolationError` (report persistence is **not** a never-throw
|
| 439 |
+
seam β `api/v1/report.py` re-raises as a 500 by design, so a lost report is loud).
|
| 440 |
+
Tell-apart: `NotNullViolationError` naming a column absent from `src/db/postgres/models.py`.
|
| 441 |
+
Fix pattern: add the column to the ORM as **nullable** (Python then matches the loosest deployment
|
| 442 |
+
shape) and always write it β never mirror one DB's NOT NULL into Python's model.
|
| 443 |
+
**Reading the Go migrations is not sufficient** β the constraint depends on the DB's history, so
|
| 444 |
+
verify the instance you actually run against:
|
| 445 |
+
`select column_name, is_nullable from information_schema.columns where table_name='<t>'`.
|
| 446 |
+
Full ORM-vs-live drift scan run 2026-07-22 against Neon: all five tables Python **writes**
|
| 447 |
+
(`reports`, `report_inputs`, `message_traceability`, `message_charts`, `analyses`) are clean.
|
| 448 |
+
Remaining drift is on read-only or dead paths and is inert β `analyses_messages` has live
|
| 449 |
+
`message_id`/`status`/`note` the ORM lacks (Go-only writer, Β§2.3; added by Go `0006` +
|
| 450 |
+
`981b42b`/`5da7283`), `documents` has a live `chunks_count`, and `message_sources` diverges wholly
|
| 451 |
+
but is referenced only by the **unwired** `api/v1/room.py`. `rooms`/`chat_messages` are absent
|
| 452 |
+
(renamed `zdeprecated_*` by `0004`).
|
| 453 |
- **Never-throw seams** are pervasive (tool invoker, query service, executors, state/catalog reads,
|
| 454 |
record persistence, report summary). Failures degrade into soft output rather than raising β good
|
| 455 |
for UX, but they can mask real breakage (e.g. a missing analysis-scope catalog silently falling
|
src/agents/report/store.py
CHANGED
|
@@ -80,6 +80,7 @@ class ReportStore:
|
|
| 80 |
AnalysisReportRow(
|
| 81 |
id=report.report_id,
|
| 82 |
analysis_id=report.analysis_id,
|
|
|
|
| 83 |
title=_report_title(report),
|
| 84 |
content=report.rendered_markdown or "",
|
| 85 |
generated_at=report.generated_at,
|
|
|
|
| 80 |
AnalysisReportRow(
|
| 81 |
id=report.report_id,
|
| 82 |
analysis_id=report.analysis_id,
|
| 83 |
+
user_id=report.user_id,
|
| 84 |
title=_report_title(report),
|
| 85 |
content=report.rendered_markdown or "",
|
| 86 |
generated_at=report.generated_at,
|
src/db/postgres/models.py
CHANGED
|
@@ -196,6 +196,10 @@ class AnalysisReportRow(Base):
|
|
| 196 |
|
| 197 |
id = Column(UUID(as_uuid=False), primary_key=True) # AnalysisReport.report_id (uuid)
|
| 198 |
analysis_id = Column(UUID(as_uuid=False), nullable=False, index=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
title = Column(String, nullable=False)
|
| 200 |
content = Column(Text, nullable=False) # rendered markdown
|
| 201 |
generated_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
|
|
|
| 196 |
|
| 197 |
id = Column(UUID(as_uuid=False), primary_key=True) # AnalysisReport.report_id (uuid)
|
| 198 |
analysis_id = Column(UUID(as_uuid=False), nullable=False, index=True)
|
| 199 |
+
# Added by Go 2026-07 (text, like `analyses.user_id`); NOT NULL in some deployments,
|
| 200 |
+
# nullable in others β declared nullable here so Python matches the loosest shape,
|
| 201 |
+
# but ReportStore always writes it (a missing value 500s on the strict DBs).
|
| 202 |
+
user_id = Column(Text)
|
| 203 |
title = Column(String, nullable=False)
|
| 204 |
content = Column(Text, nullable=False) # rendered markdown
|
| 205 |
generated_at = Column(DateTime(timezone=True), nullable=False, server_default=func.now())
|