| """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"]) |
|
|
| |
| _store = PostgresChartStore() |
|
|
|
|
| class ChartsResponse(BaseModel): |
| status: Literal["success", "empty", "not_found"] |
| message: str |
| count: int |
| charts: list[ChartRecord] |
|
|
|
|
| @router.get("/charts", response_model=ChartsResponse) |
| @log_execution(logger) |
| 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=[], |
| ) |
|
|