AI-coach SSE stream for a dashboard
const url = 'http://localhost:8000/api/dashboard/coaching/?format=json';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"context":"overview","lang":"en","payload":{"range_days":14,"total_clients":42,"active_clients":18,"pending_review":3,"failed_7d":0,"roster_avg":78,"at_risk":8,"streaks":9,"movers_up":[{"client":"Anna Greco","activity":"running","delta":12}],"movers_down":[]}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url 'http://localhost:8000/api/dashboard/coaching/?format=json' \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "context": "overview", "lang": "en", "payload": { "range_days": 14, "total_clients": 42, "active_clients": 18, "pending_review": 3, "failed_7d": 0, "roster_avg": 78, "at_risk": 8, "streaks": 9, "movers_up": [ { "client": "Anna Greco", "activity": "running", "delta": 12 } ], "movers_down": [] } }'Streams a richer DeepSeek narrative on top of the rule-based recommendations field of every dashboard response. POST a small payload summarising the dashboard context — the server hands it to DeepSeek with a coach-facing system prompt and streams tokens back via Server-Sent Events.
Response is Content-Type: text/event-stream. The view returns StreamingHttpResponse directly (no CSRF — DRF APIView is CSRF-exempt).
Wire format — four event types:
event: meta(1 frame, first) —{context, lang, model, provider, mode}.mode="live"means live DeepSeek;mode="stub"means deterministic local fallback (dev or noLLM_API_KEY).event: token(N frames) —{text: "<chunk>"}— concatenatetextacross frames to assemble the markdown.event: done(1 frame, last) —{ok: true}. After this the connection closes.event: warn(rare) — emitted on LLM errors before falling through to the stub:{detail: "<reason>"}.
Context-payload contracts — what the backend expects in payload per context:
overview—{range_days, total_clients, active_clients, pending_review, failed_7d, roster_avg, at_risk, streaks, movers_up:[{client,activity,delta}], movers_down:[...]}.activity—{activity, range_days, metric_label, metric_unit, cohort_median, cohort_p25, cohort_p75, leaderboard_count, outliers:[{client_name,metric,value,direction}]}.client—{activity, metric_label, metric_unit, target_min, target_max, series_tail:[number|null]*, zone_tail:[bool|null]*}(last 6).business—{payment_status, plan_name, quota_remaining, quota_granted, coach_productivity:[{name,role,sessions,pending}], activity_mix:[{activity,count,fraction}]}.
Source-of-truth: apps/api/aikynetix/dashboard/coaching.py::_user_prompt_for_context.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Query Parameters
Section titled “Query Parameters”Request Bodyrequired
Section titled “Request Bodyrequired”object
Which dashboard the prompt is built for.
overview- overviewactivity- activityclient- clientbusiness- business
Context-specific dict — see the description for the per-context contract. Pre-aggregated client-side from the dashboard response, so the server doesn’t re-fetch.
Output language for the narrative.
en- enru- ru
Examples
Overview body
{ "context": "overview", "lang": "en", "payload": { "range_days": 14, "total_clients": 42, "active_clients": 18, "pending_review": 3, "failed_7d": 0, "roster_avg": 78, "at_risk": 8, "streaks": 9, "movers_up": [ { "client": "Anna Greco", "activity": "running", "delta": 12 } ], "movers_down": [] }}object
Which dashboard the prompt is built for.
overview- overviewactivity- activityclient- clientbusiness- business
Context-specific dict — see the description for the per-context contract. Pre-aggregated client-side from the dashboard response, so the server doesn’t re-fetch.
Output language for the narrative.
en- enru- ru
object
Which dashboard the prompt is built for.
overview- overviewactivity- activityclient- clientbusiness- business
Context-specific dict — see the description for the per-context contract. Pre-aggregated client-side from the dashboard response, so the server doesn’t re-fetch.
Output language for the narrative.
en- enru- ru
Responses
Section titled “Responses”A text/event-stream (SSE) response. Frames: one meta, N token ({text}), a final done {ok: true}, and a rare warn {detail} if the LLM falls back to the stub.
Examples
SSE wire (excerpt)
event: metadata: {"context":"overview","lang":"en","model":"deepseek-v4-flash","provider":"deepseek","mode":"live"}
event: tokendata: {"text":"- Triage 3 sessions waiting "}
event: tokendata: {"text":"in review.\n"}
event: donedata: {"ok":true}object
Examplegenerated
{ "context": [ "example" ]}object
object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).
Examplegenerated
{ "detail": "example"}object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).