Rifqi Hafizuddin
[NOTICKET] revert: drop F-3 user_id scoping on /charts + /traceability (lead decision)
d998370 | """Charts endpoint β user-facing chart retrieval for one turn (S2 visualization, | |
| SPINE_V2_PLAN Β§4.4/Β§4.5). | |
| `GET /api/v1/charts?message_id=` returns every chart a `render_chart` tool call | |
| produced during one assistant turn, as `dataeyond.chart.v1` envelopes | |
| (SPINE_V2_PLAN Β§4.2). Rows are written by the chat pipeline right before the `done` | |
| SSE event; the FE fires this GET on `done` with the `message_id` from that event. | |
| Lookup is by `message_id` alone (Python-minted UUID4 β lead decision 2026-07-13). | |
| Every response is HTTP 200 with an explicit `status` marker (lead ask 2026-07-13): | |
| - `success` β β₯1 chart; render them. | |
| - `empty` β the turn completed but produced no charts (the common case). | |
| - `not_found` β no completed turn is known for this message_id (mistyped/stale | |
| id, or an error turn β those never write a row). Distinguished | |
| from `empty` via the turn's traceability row. | |
| No auth β Go fronts Python. | |
| """ | |
| from typing import Literal | |
| from fastapi import APIRouter, Query | |
| from pydantic import BaseModel | |
| from src.charts import ChartRecord, PostgresChartStore | |
| from src.middlewares.logging import get_logger, log_execution | |
| logger = get_logger("charts_api") | |
| router = APIRouter(prefix="/api/v1", tags=["Charts"]) | |
| # Warm, process-shared store (mirrors the chat endpoints' module-level instances). | |
| _store = PostgresChartStore() | |
| class ChartsResponse(BaseModel): | |
| status: Literal["success", "empty", "not_found"] | |
| message: str | |
| count: int | |
| charts: list[ChartRecord] | |
| async def get_charts( | |
| message_id: str = Query( | |
| ..., description="Assistant turn id, taken from the `done` SSE event" | |
| ), | |
| ) -> ChartsResponse: | |
| """Fetch every chart for one turn. Always 200 β the `status` field carries the | |
| outcome, so the FE can call this unconditionally on every `done`.""" | |
| charts = await _store.list_for_message(message_id) | |
| if charts: | |
| return ChartsResponse( | |
| status="success", | |
| message=f"{len(charts)} chart(s) for this message.", | |
| count=len(charts), | |
| charts=charts, | |
| ) | |
| if await _store.turn_exists(message_id): | |
| return ChartsResponse( | |
| status="empty", | |
| message="This message completed without producing charts.", | |
| count=0, | |
| charts=[], | |
| ) | |
| return ChartsResponse( | |
| status="not_found", | |
| message="No completed turn is known for this message_id.", | |
| count=0, | |
| charts=[], | |
| ) | |